mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
899 lines
22 KiB
899 lines
22 KiB
// SPDX-License-Identifier: LGPL-2.1 |
|
/* |
|
* fs/cifs/smb2transport.c |
|
* |
|
* Copyright (C) International Business Machines Corp., 2002, 2011 |
|
* Etersoft, 2012 |
|
* Author(s): Steve French ([email protected]) |
|
* Jeremy Allison ([email protected]) 2006 |
|
* Pavel Shilovsky ([email protected]) 2012 |
|
* |
|
*/ |
|
|
|
#include <linux/fs.h> |
|
#include <linux/list.h> |
|
#include <linux/wait.h> |
|
#include <linux/net.h> |
|
#include <linux/delay.h> |
|
#include <linux/uaccess.h> |
|
#include <asm/processor.h> |
|
#include <linux/mempool.h> |
|
#include <linux/highmem.h> |
|
#include <crypto/aead.h> |
|
#include "smb2pdu.h" |
|
#include "cifsglob.h" |
|
#include "cifsproto.h" |
|
#include "smb2proto.h" |
|
#include "cifs_debug.h" |
|
#include "smb2status.h" |
|
#include "smb2glob.h" |
|
|
|
static int |
|
smb3_crypto_shash_allocate(struct TCP_Server_Info *server) |
|
{ |
|
struct cifs_secmech *p = &server->secmech; |
|
int rc; |
|
|
|
rc = cifs_alloc_hash("hmac(sha256)", |
|
&p->hmacsha256, |
|
&p->sdeschmacsha256); |
|
if (rc) |
|
goto err; |
|
|
|
rc = cifs_alloc_hash("cmac(aes)", &p->cmacaes, &p->sdesccmacaes); |
|
if (rc) |
|
goto err; |
|
|
|
return 0; |
|
err: |
|
cifs_free_hash(&p->hmacsha256, &p->sdeschmacsha256); |
|
return rc; |
|
} |
|
|
|
int |
|
smb311_crypto_shash_allocate(struct TCP_Server_Info *server) |
|
{ |
|
struct cifs_secmech *p = &server->secmech; |
|
int rc = 0; |
|
|
|
rc = cifs_alloc_hash("hmac(sha256)", |
|
&p->hmacsha256, |
|
&p->sdeschmacsha256); |
|
if (rc) |
|
return rc; |
|
|
|
rc = cifs_alloc_hash("cmac(aes)", &p->cmacaes, &p->sdesccmacaes); |
|
if (rc) |
|
goto err; |
|
|
|
rc = cifs_alloc_hash("sha512", &p->sha512, &p->sdescsha512); |
|
if (rc) |
|
goto err; |
|
|
|
return 0; |
|
|
|
err: |
|
cifs_free_hash(&p->cmacaes, &p->sdesccmacaes); |
|
cifs_free_hash(&p->hmacsha256, &p->sdeschmacsha256); |
|
return rc; |
|
} |
|
|
|
|
|
static |
|
int smb2_get_sign_key(__u64 ses_id, struct TCP_Server_Info *server, u8 *key) |
|
{ |
|
struct cifs_chan *chan; |
|
struct cifs_ses *ses = NULL; |
|
struct TCP_Server_Info *it = NULL; |
|
int i; |
|
int rc = 0; |
|
|
|
spin_lock(&cifs_tcp_ses_lock); |
|
|
|
list_for_each_entry(it, &cifs_tcp_ses_list, tcp_ses_list) { |
|
list_for_each_entry(ses, &it->smb_ses_list, smb_ses_list) { |
|
if (ses->Suid == ses_id) |
|
goto found; |
|
} |
|
} |
|
cifs_server_dbg(VFS, "%s: Could not find session 0x%llx\n", |
|
__func__, ses_id); |
|
rc = -ENOENT; |
|
goto out; |
|
|
|
found: |
|
if (ses->binding) { |
|
/* |
|
* If we are in the process of binding a new channel |
|
* to an existing session, use the master connection |
|
* session key |
|
*/ |
|
memcpy(key, ses->smb3signingkey, SMB3_SIGN_KEY_SIZE); |
|
goto out; |
|
} |
|
|
|
/* |
|
* Otherwise, use the channel key. |
|
*/ |
|
|
|
for (i = 0; i < ses->chan_count; i++) { |
|
chan = ses->chans + i; |
|
if (chan->server == server) { |
|
memcpy(key, chan->signkey, SMB3_SIGN_KEY_SIZE); |
|
goto out; |
|
} |
|
} |
|
|
|
cifs_dbg(VFS, |
|
"%s: Could not find channel signing key for session 0x%llx\n", |
|
__func__, ses_id); |
|
rc = -ENOENT; |
|
|
|
out: |
|
spin_unlock(&cifs_tcp_ses_lock); |
|
return rc; |
|
} |
|
|
|
static struct cifs_ses * |
|
smb2_find_smb_ses_unlocked(struct TCP_Server_Info *server, __u64 ses_id) |
|
{ |
|
struct cifs_ses *ses; |
|
|
|
list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) { |
|
if (ses->Suid != ses_id) |
|
continue; |
|
++ses->ses_count; |
|
return ses; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
struct cifs_ses * |
|
smb2_find_smb_ses(struct TCP_Server_Info *server, __u64 ses_id) |
|
{ |
|
struct cifs_ses *ses; |
|
|
|
spin_lock(&cifs_tcp_ses_lock); |
|
ses = smb2_find_smb_ses_unlocked(server, ses_id); |
|
spin_unlock(&cifs_tcp_ses_lock); |
|
|
|
return ses; |
|
} |
|
|
|
static struct cifs_tcon * |
|
smb2_find_smb_sess_tcon_unlocked(struct cifs_ses *ses, __u32 tid) |
|
{ |
|
struct cifs_tcon *tcon; |
|
|
|
list_for_each_entry(tcon, &ses->tcon_list, tcon_list) { |
|
if (tcon->tid != tid) |
|
continue; |
|
++tcon->tc_count; |
|
return tcon; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
/* |
|
* Obtain tcon corresponding to the tid in the given |
|
* cifs_ses |
|
*/ |
|
|
|
struct cifs_tcon * |
|
smb2_find_smb_tcon(struct TCP_Server_Info *server, __u64 ses_id, __u32 tid) |
|
{ |
|
struct cifs_ses *ses; |
|
struct cifs_tcon *tcon; |
|
|
|
spin_lock(&cifs_tcp_ses_lock); |
|
ses = smb2_find_smb_ses_unlocked(server, ses_id); |
|
if (!ses) { |
|
spin_unlock(&cifs_tcp_ses_lock); |
|
return NULL; |
|
} |
|
tcon = smb2_find_smb_sess_tcon_unlocked(ses, tid); |
|
if (!tcon) { |
|
cifs_put_smb_ses(ses); |
|
spin_unlock(&cifs_tcp_ses_lock); |
|
return NULL; |
|
} |
|
spin_unlock(&cifs_tcp_ses_lock); |
|
/* tcon already has a ref to ses, so we don't need ses anymore */ |
|
cifs_put_smb_ses(ses); |
|
|
|
return tcon; |
|
} |
|
|
|
int |
|
smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server, |
|
bool allocate_crypto) |
|
{ |
|
int rc; |
|
unsigned char smb2_signature[SMB2_HMACSHA256_SIZE]; |
|
unsigned char *sigptr = smb2_signature; |
|
struct kvec *iov = rqst->rq_iov; |
|
struct smb2_sync_hdr *shdr = (struct smb2_sync_hdr *)iov[0].iov_base; |
|
struct cifs_ses *ses; |
|
struct shash_desc *shash; |
|
struct crypto_shash *hash; |
|
struct sdesc *sdesc = NULL; |
|
struct smb_rqst drqst; |
|
|
|
ses = smb2_find_smb_ses(server, shdr->SessionId); |
|
if (!ses) { |
|
cifs_server_dbg(VFS, "%s: Could not find session\n", __func__); |
|
return 0; |
|
} |
|
|
|
memset(smb2_signature, 0x0, SMB2_HMACSHA256_SIZE); |
|
memset(shdr->Signature, 0x0, SMB2_SIGNATURE_SIZE); |
|
|
|
if (allocate_crypto) { |
|
rc = cifs_alloc_hash("hmac(sha256)", &hash, &sdesc); |
|
if (rc) { |
|
cifs_server_dbg(VFS, |
|
"%s: sha256 alloc failed\n", __func__); |
|
goto out; |
|
} |
|
shash = &sdesc->shash; |
|
} else { |
|
hash = server->secmech.hmacsha256; |
|
shash = &server->secmech.sdeschmacsha256->shash; |
|
} |
|
|
|
rc = crypto_shash_setkey(hash, ses->auth_key.response, |
|
SMB2_NTLMV2_SESSKEY_SIZE); |
|
if (rc) { |
|
cifs_server_dbg(VFS, |
|
"%s: Could not update with response\n", |
|
__func__); |
|
goto out; |
|
} |
|
|
|
rc = crypto_shash_init(shash); |
|
if (rc) { |
|
cifs_server_dbg(VFS, "%s: Could not init sha256", __func__); |
|
goto out; |
|
} |
|
|
|
/* |
|
* For SMB2+, __cifs_calc_signature() expects to sign only the actual |
|
* data, that is, iov[0] should not contain a rfc1002 length. |
|
* |
|
* Sign the rfc1002 length prior to passing the data (iov[1-N]) down to |
|
* __cifs_calc_signature(). |
|
*/ |
|
drqst = *rqst; |
|
if (drqst.rq_nvec >= 2 && iov[0].iov_len == 4) { |
|
rc = crypto_shash_update(shash, iov[0].iov_base, |
|
iov[0].iov_len); |
|
if (rc) { |
|
cifs_server_dbg(VFS, |
|
"%s: Could not update with payload\n", |
|
__func__); |
|
goto out; |
|
} |
|
drqst.rq_iov++; |
|
drqst.rq_nvec--; |
|
} |
|
|
|
rc = __cifs_calc_signature(&drqst, server, sigptr, shash); |
|
if (!rc) |
|
memcpy(shdr->Signature, sigptr, SMB2_SIGNATURE_SIZE); |
|
|
|
out: |
|
if (allocate_crypto) |
|
cifs_free_hash(&hash, &sdesc); |
|
if (ses) |
|
cifs_put_smb_ses(ses); |
|
return rc; |
|
} |
|
|
|
static int generate_key(struct cifs_ses *ses, struct kvec label, |
|
struct kvec context, __u8 *key, unsigned int key_size) |
|
{ |
|
unsigned char zero = 0x0; |
|
__u8 i[4] = {0, 0, 0, 1}; |
|
__u8 L128[4] = {0, 0, 0, 128}; |
|
__u8 L256[4] = {0, 0, 1, 0}; |
|
int rc = 0; |
|
unsigned char prfhash[SMB2_HMACSHA256_SIZE]; |
|
unsigned char *hashptr = prfhash; |
|
struct TCP_Server_Info *server = ses->server; |
|
|
|
memset(prfhash, 0x0, SMB2_HMACSHA256_SIZE); |
|
memset(key, 0x0, key_size); |
|
|
|
rc = smb3_crypto_shash_allocate(server); |
|
if (rc) { |
|
cifs_server_dbg(VFS, "%s: crypto alloc failed\n", __func__); |
|
goto smb3signkey_ret; |
|
} |
|
|
|
rc = crypto_shash_setkey(server->secmech.hmacsha256, |
|
ses->auth_key.response, SMB2_NTLMV2_SESSKEY_SIZE); |
|
if (rc) { |
|
cifs_server_dbg(VFS, "%s: Could not set with session key\n", __func__); |
|
goto smb3signkey_ret; |
|
} |
|
|
|
rc = crypto_shash_init(&server->secmech.sdeschmacsha256->shash); |
|
if (rc) { |
|
cifs_server_dbg(VFS, "%s: Could not init sign hmac\n", __func__); |
|
goto smb3signkey_ret; |
|
} |
|
|
|
rc = crypto_shash_update(&server->secmech.sdeschmacsha256->shash, |
|
i, 4); |
|
if (rc) { |
|
cifs_server_dbg(VFS, "%s: Could not update with n\n", __func__); |
|
goto smb3signkey_ret; |
|
} |
|
|
|
rc = crypto_shash_update(&server->secmech.sdeschmacsha256->shash, |
|
label.iov_base, label.iov_len); |
|
if (rc) { |
|
cifs_server_dbg(VFS, "%s: Could not update with label\n", __func__); |
|
goto smb3signkey_ret; |
|
} |
|
|
|
rc = crypto_shash_update(&server->secmech.sdeschmacsha256->shash, |
|
&zero, 1); |
|
if (rc) { |
|
cifs_server_dbg(VFS, "%s: Could not update with zero\n", __func__); |
|
goto smb3signkey_ret; |
|
} |
|
|
|
rc = crypto_shash_update(&server->secmech.sdeschmacsha256->shash, |
|
context.iov_base, context.iov_len); |
|
if (rc) { |
|
cifs_server_dbg(VFS, "%s: Could not update with context\n", __func__); |
|
goto smb3signkey_ret; |
|
} |
|
|
|
if ((server->cipher_type == SMB2_ENCRYPTION_AES256_CCM) || |
|
(server->cipher_type == SMB2_ENCRYPTION_AES256_GCM)) { |
|
rc = crypto_shash_update(&server->secmech.sdeschmacsha256->shash, |
|
L256, 4); |
|
} else { |
|
rc = crypto_shash_update(&server->secmech.sdeschmacsha256->shash, |
|
L128, 4); |
|
} |
|
if (rc) { |
|
cifs_server_dbg(VFS, "%s: Could not update with L\n", __func__); |
|
goto smb3signkey_ret; |
|
} |
|
|
|
rc = crypto_shash_final(&server->secmech.sdeschmacsha256->shash, |
|
hashptr); |
|
if (rc) { |
|
cifs_server_dbg(VFS, "%s: Could not generate sha256 hash\n", __func__); |
|
goto smb3signkey_ret; |
|
} |
|
|
|
memcpy(key, hashptr, key_size); |
|
|
|
smb3signkey_ret: |
|
return rc; |
|
} |
|
|
|
struct derivation { |
|
struct kvec label; |
|
struct kvec context; |
|
}; |
|
|
|
struct derivation_triplet { |
|
struct derivation signing; |
|
struct derivation encryption; |
|
struct derivation decryption; |
|
}; |
|
|
|
static int |
|
generate_smb3signingkey(struct cifs_ses *ses, |
|
const struct derivation_triplet *ptriplet) |
|
{ |
|
int rc; |
|
#ifdef CONFIG_CIFS_DEBUG_DUMP_KEYS |
|
struct TCP_Server_Info *server = ses->server; |
|
#endif |
|
|
|
/* |
|
* All channels use the same encryption/decryption keys but |
|
* they have their own signing key. |
|
* |
|
* When we generate the keys, check if it is for a new channel |
|
* (binding) in which case we only need to generate a signing |
|
* key and store it in the channel as to not overwrite the |
|
* master connection signing key stored in the session |
|
*/ |
|
|
|
if (ses->binding) { |
|
rc = generate_key(ses, ptriplet->signing.label, |
|
ptriplet->signing.context, |
|
cifs_ses_binding_channel(ses)->signkey, |
|
SMB3_SIGN_KEY_SIZE); |
|
if (rc) |
|
return rc; |
|
} else { |
|
rc = generate_key(ses, ptriplet->signing.label, |
|
ptriplet->signing.context, |
|
ses->smb3signingkey, |
|
SMB3_SIGN_KEY_SIZE); |
|
if (rc) |
|
return rc; |
|
|
|
memcpy(ses->chans[0].signkey, ses->smb3signingkey, |
|
SMB3_SIGN_KEY_SIZE); |
|
|
|
rc = generate_key(ses, ptriplet->encryption.label, |
|
ptriplet->encryption.context, |
|
ses->smb3encryptionkey, |
|
SMB3_ENC_DEC_KEY_SIZE); |
|
rc = generate_key(ses, ptriplet->decryption.label, |
|
ptriplet->decryption.context, |
|
ses->smb3decryptionkey, |
|
SMB3_ENC_DEC_KEY_SIZE); |
|
if (rc) |
|
return rc; |
|
} |
|
|
|
if (rc) |
|
return rc; |
|
|
|
#ifdef CONFIG_CIFS_DEBUG_DUMP_KEYS |
|
cifs_dbg(VFS, "%s: dumping generated AES session keys\n", __func__); |
|
/* |
|
* The session id is opaque in terms of endianness, so we can't |
|
* print it as a long long. we dump it as we got it on the wire |
|
*/ |
|
cifs_dbg(VFS, "Session Id %*ph\n", (int)sizeof(ses->Suid), |
|
&ses->Suid); |
|
cifs_dbg(VFS, "Cipher type %d\n", server->cipher_type); |
|
cifs_dbg(VFS, "Session Key %*ph\n", |
|
SMB2_NTLMV2_SESSKEY_SIZE, ses->auth_key.response); |
|
cifs_dbg(VFS, "Signing Key %*ph\n", |
|
SMB3_SIGN_KEY_SIZE, ses->smb3signingkey); |
|
if ((server->cipher_type == SMB2_ENCRYPTION_AES256_CCM) || |
|
(server->cipher_type == SMB2_ENCRYPTION_AES256_GCM)) { |
|
cifs_dbg(VFS, "ServerIn Key %*ph\n", |
|
SMB3_GCM256_CRYPTKEY_SIZE, ses->smb3encryptionkey); |
|
cifs_dbg(VFS, "ServerOut Key %*ph\n", |
|
SMB3_GCM256_CRYPTKEY_SIZE, ses->smb3decryptionkey); |
|
} else { |
|
cifs_dbg(VFS, "ServerIn Key %*ph\n", |
|
SMB3_GCM128_CRYPTKEY_SIZE, ses->smb3encryptionkey); |
|
cifs_dbg(VFS, "ServerOut Key %*ph\n", |
|
SMB3_GCM128_CRYPTKEY_SIZE, ses->smb3decryptionkey); |
|
} |
|
#endif |
|
return rc; |
|
} |
|
|
|
int |
|
generate_smb30signingkey(struct cifs_ses *ses) |
|
|
|
{ |
|
struct derivation_triplet triplet; |
|
struct derivation *d; |
|
|
|
d = &triplet.signing; |
|
d->label.iov_base = "SMB2AESCMAC"; |
|
d->label.iov_len = 12; |
|
d->context.iov_base = "SmbSign"; |
|
d->context.iov_len = 8; |
|
|
|
d = &triplet.encryption; |
|
d->label.iov_base = "SMB2AESCCM"; |
|
d->label.iov_len = 11; |
|
d->context.iov_base = "ServerIn "; |
|
d->context.iov_len = 10; |
|
|
|
d = &triplet.decryption; |
|
d->label.iov_base = "SMB2AESCCM"; |
|
d->label.iov_len = 11; |
|
d->context.iov_base = "ServerOut"; |
|
d->context.iov_len = 10; |
|
|
|
return generate_smb3signingkey(ses, &triplet); |
|
} |
|
|
|
int |
|
generate_smb311signingkey(struct cifs_ses *ses) |
|
|
|
{ |
|
struct derivation_triplet triplet; |
|
struct derivation *d; |
|
|
|
d = &triplet.signing; |
|
d->label.iov_base = "SMBSigningKey"; |
|
d->label.iov_len = 14; |
|
d->context.iov_base = ses->preauth_sha_hash; |
|
d->context.iov_len = 64; |
|
|
|
d = &triplet.encryption; |
|
d->label.iov_base = "SMBC2SCipherKey"; |
|
d->label.iov_len = 16; |
|
d->context.iov_base = ses->preauth_sha_hash; |
|
d->context.iov_len = 64; |
|
|
|
d = &triplet.decryption; |
|
d->label.iov_base = "SMBS2CCipherKey"; |
|
d->label.iov_len = 16; |
|
d->context.iov_base = ses->preauth_sha_hash; |
|
d->context.iov_len = 64; |
|
|
|
return generate_smb3signingkey(ses, &triplet); |
|
} |
|
|
|
int |
|
smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server, |
|
bool allocate_crypto) |
|
{ |
|
int rc; |
|
unsigned char smb3_signature[SMB2_CMACAES_SIZE]; |
|
unsigned char *sigptr = smb3_signature; |
|
struct kvec *iov = rqst->rq_iov; |
|
struct smb2_sync_hdr *shdr = (struct smb2_sync_hdr *)iov[0].iov_base; |
|
struct shash_desc *shash; |
|
struct crypto_shash *hash; |
|
struct sdesc *sdesc = NULL; |
|
struct smb_rqst drqst; |
|
u8 key[SMB3_SIGN_KEY_SIZE]; |
|
|
|
rc = smb2_get_sign_key(shdr->SessionId, server, key); |
|
if (rc) |
|
return 0; |
|
|
|
if (allocate_crypto) { |
|
rc = cifs_alloc_hash("cmac(aes)", &hash, &sdesc); |
|
if (rc) |
|
return rc; |
|
|
|
shash = &sdesc->shash; |
|
} else { |
|
hash = server->secmech.cmacaes; |
|
shash = &server->secmech.sdesccmacaes->shash; |
|
} |
|
|
|
memset(smb3_signature, 0x0, SMB2_CMACAES_SIZE); |
|
memset(shdr->Signature, 0x0, SMB2_SIGNATURE_SIZE); |
|
|
|
rc = crypto_shash_setkey(hash, key, SMB2_CMACAES_SIZE); |
|
if (rc) { |
|
cifs_server_dbg(VFS, "%s: Could not set key for cmac aes\n", __func__); |
|
goto out; |
|
} |
|
|
|
/* |
|
* we already allocate sdesccmacaes when we init smb3 signing key, |
|
* so unlike smb2 case we do not have to check here if secmech are |
|
* initialized |
|
*/ |
|
rc = crypto_shash_init(shash); |
|
if (rc) { |
|
cifs_server_dbg(VFS, "%s: Could not init cmac aes\n", __func__); |
|
goto out; |
|
} |
|
|
|
/* |
|
* For SMB2+, __cifs_calc_signature() expects to sign only the actual |
|
* data, that is, iov[0] should not contain a rfc1002 length. |
|
* |
|
* Sign the rfc1002 length prior to passing the data (iov[1-N]) down to |
|
* __cifs_calc_signature(). |
|
*/ |
|
drqst = *rqst; |
|
if (drqst.rq_nvec >= 2 && iov[0].iov_len == 4) { |
|
rc = crypto_shash_update(shash, iov[0].iov_base, |
|
iov[0].iov_len); |
|
if (rc) { |
|
cifs_server_dbg(VFS, "%s: Could not update with payload\n", |
|
__func__); |
|
goto out; |
|
} |
|
drqst.rq_iov++; |
|
drqst.rq_nvec--; |
|
} |
|
|
|
rc = __cifs_calc_signature(&drqst, server, sigptr, shash); |
|
if (!rc) |
|
memcpy(shdr->Signature, sigptr, SMB2_SIGNATURE_SIZE); |
|
|
|
out: |
|
if (allocate_crypto) |
|
cifs_free_hash(&hash, &sdesc); |
|
return rc; |
|
} |
|
|
|
/* must be called with server->srv_mutex held */ |
|
static int |
|
smb2_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server) |
|
{ |
|
int rc = 0; |
|
struct smb2_sync_hdr *shdr; |
|
struct smb2_sess_setup_req *ssr; |
|
bool is_binding; |
|
bool is_signed; |
|
|
|
shdr = (struct smb2_sync_hdr *)rqst->rq_iov[0].iov_base; |
|
ssr = (struct smb2_sess_setup_req *)shdr; |
|
|
|
is_binding = shdr->Command == SMB2_SESSION_SETUP && |
|
(ssr->Flags & SMB2_SESSION_REQ_FLAG_BINDING); |
|
is_signed = shdr->Flags & SMB2_FLAGS_SIGNED; |
|
|
|
if (!is_signed) |
|
return 0; |
|
if (server->tcpStatus == CifsNeedNegotiate) |
|
return 0; |
|
if (!is_binding && !server->session_estab) { |
|
strncpy(shdr->Signature, "BSRSPYL", 8); |
|
return 0; |
|
} |
|
|
|
rc = server->ops->calc_signature(rqst, server, false); |
|
|
|
return rc; |
|
} |
|
|
|
int |
|
smb2_verify_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server) |
|
{ |
|
unsigned int rc; |
|
char server_response_sig[SMB2_SIGNATURE_SIZE]; |
|
struct smb2_sync_hdr *shdr = |
|
(struct smb2_sync_hdr *)rqst->rq_iov[0].iov_base; |
|
|
|
if ((shdr->Command == SMB2_NEGOTIATE) || |
|
(shdr->Command == SMB2_SESSION_SETUP) || |
|
(shdr->Command == SMB2_OPLOCK_BREAK) || |
|
server->ignore_signature || |
|
(!server->session_estab)) |
|
return 0; |
|
|
|
/* |
|
* BB what if signatures are supposed to be on for session but |
|
* server does not send one? BB |
|
*/ |
|
|
|
/* Do not need to verify session setups with signature "BSRSPYL " */ |
|
if (memcmp(shdr->Signature, "BSRSPYL ", 8) == 0) |
|
cifs_dbg(FYI, "dummy signature received for smb command 0x%x\n", |
|
shdr->Command); |
|
|
|
/* |
|
* Save off the origiginal signature so we can modify the smb and check |
|
* our calculated signature against what the server sent. |
|
*/ |
|
memcpy(server_response_sig, shdr->Signature, SMB2_SIGNATURE_SIZE); |
|
|
|
memset(shdr->Signature, 0, SMB2_SIGNATURE_SIZE); |
|
|
|
rc = server->ops->calc_signature(rqst, server, true); |
|
|
|
if (rc) |
|
return rc; |
|
|
|
if (memcmp(server_response_sig, shdr->Signature, SMB2_SIGNATURE_SIZE)) { |
|
cifs_dbg(VFS, "sign fail cmd 0x%x message id 0x%llx\n", |
|
shdr->Command, shdr->MessageId); |
|
return -EACCES; |
|
} else |
|
return 0; |
|
} |
|
|
|
/* |
|
* Set message id for the request. Should be called after wait_for_free_request |
|
* and when srv_mutex is held. |
|
*/ |
|
static inline void |
|
smb2_seq_num_into_buf(struct TCP_Server_Info *server, |
|
struct smb2_sync_hdr *shdr) |
|
{ |
|
unsigned int i, num = le16_to_cpu(shdr->CreditCharge); |
|
|
|
shdr->MessageId = get_next_mid64(server); |
|
/* skip message numbers according to CreditCharge field */ |
|
for (i = 1; i < num; i++) |
|
get_next_mid(server); |
|
} |
|
|
|
static struct mid_q_entry * |
|
smb2_mid_entry_alloc(const struct smb2_sync_hdr *shdr, |
|
struct TCP_Server_Info *server) |
|
{ |
|
struct mid_q_entry *temp; |
|
unsigned int credits = le16_to_cpu(shdr->CreditCharge); |
|
|
|
if (server == NULL) { |
|
cifs_dbg(VFS, "Null TCP session in smb2_mid_entry_alloc\n"); |
|
return NULL; |
|
} |
|
|
|
temp = mempool_alloc(cifs_mid_poolp, GFP_NOFS); |
|
memset(temp, 0, sizeof(struct mid_q_entry)); |
|
kref_init(&temp->refcount); |
|
temp->mid = le64_to_cpu(shdr->MessageId); |
|
temp->credits = credits > 0 ? credits : 1; |
|
temp->pid = current->pid; |
|
temp->command = shdr->Command; /* Always LE */ |
|
temp->when_alloc = jiffies; |
|
temp->server = server; |
|
|
|
/* |
|
* The default is for the mid to be synchronous, so the |
|
* default callback just wakes up the current task. |
|
*/ |
|
get_task_struct(current); |
|
temp->creator = current; |
|
temp->callback = cifs_wake_up_task; |
|
temp->callback_data = current; |
|
|
|
atomic_inc(&midCount); |
|
temp->mid_state = MID_REQUEST_ALLOCATED; |
|
trace_smb3_cmd_enter(shdr->TreeId, shdr->SessionId, |
|
le16_to_cpu(shdr->Command), temp->mid); |
|
return temp; |
|
} |
|
|
|
static int |
|
smb2_get_mid_entry(struct cifs_ses *ses, struct TCP_Server_Info *server, |
|
struct smb2_sync_hdr *shdr, struct mid_q_entry **mid) |
|
{ |
|
if (server->tcpStatus == CifsExiting) |
|
return -ENOENT; |
|
|
|
if (server->tcpStatus == CifsNeedReconnect) { |
|
cifs_dbg(FYI, "tcp session dead - return to caller to retry\n"); |
|
return -EAGAIN; |
|
} |
|
|
|
if (server->tcpStatus == CifsNeedNegotiate && |
|
shdr->Command != SMB2_NEGOTIATE) |
|
return -EAGAIN; |
|
|
|
if (ses->status == CifsNew) { |
|
if ((shdr->Command != SMB2_SESSION_SETUP) && |
|
(shdr->Command != SMB2_NEGOTIATE)) |
|
return -EAGAIN; |
|
/* else ok - we are setting up session */ |
|
} |
|
|
|
if (ses->status == CifsExiting) { |
|
if (shdr->Command != SMB2_LOGOFF) |
|
return -EAGAIN; |
|
/* else ok - we are shutting down the session */ |
|
} |
|
|
|
*mid = smb2_mid_entry_alloc(shdr, server); |
|
if (*mid == NULL) |
|
return -ENOMEM; |
|
spin_lock(&GlobalMid_Lock); |
|
list_add_tail(&(*mid)->qhead, &server->pending_mid_q); |
|
spin_unlock(&GlobalMid_Lock); |
|
|
|
return 0; |
|
} |
|
|
|
int |
|
smb2_check_receive(struct mid_q_entry *mid, struct TCP_Server_Info *server, |
|
bool log_error) |
|
{ |
|
unsigned int len = mid->resp_buf_size; |
|
struct kvec iov[1]; |
|
struct smb_rqst rqst = { .rq_iov = iov, |
|
.rq_nvec = 1 }; |
|
|
|
iov[0].iov_base = (char *)mid->resp_buf; |
|
iov[0].iov_len = len; |
|
|
|
dump_smb(mid->resp_buf, min_t(u32, 80, len)); |
|
/* convert the length into a more usable form */ |
|
if (len > 24 && server->sign && !mid->decrypted) { |
|
int rc; |
|
|
|
rc = smb2_verify_signature(&rqst, server); |
|
if (rc) |
|
cifs_server_dbg(VFS, "SMB signature verification returned error = %d\n", |
|
rc); |
|
} |
|
|
|
return map_smb2_to_linux_error(mid->resp_buf, log_error); |
|
} |
|
|
|
struct mid_q_entry * |
|
smb2_setup_request(struct cifs_ses *ses, struct TCP_Server_Info *server, |
|
struct smb_rqst *rqst) |
|
{ |
|
int rc; |
|
struct smb2_sync_hdr *shdr = |
|
(struct smb2_sync_hdr *)rqst->rq_iov[0].iov_base; |
|
struct mid_q_entry *mid; |
|
|
|
smb2_seq_num_into_buf(server, shdr); |
|
|
|
rc = smb2_get_mid_entry(ses, server, shdr, &mid); |
|
if (rc) { |
|
revert_current_mid_from_hdr(server, shdr); |
|
return ERR_PTR(rc); |
|
} |
|
|
|
rc = smb2_sign_rqst(rqst, server); |
|
if (rc) { |
|
revert_current_mid_from_hdr(server, shdr); |
|
cifs_delete_mid(mid); |
|
return ERR_PTR(rc); |
|
} |
|
|
|
return mid; |
|
} |
|
|
|
struct mid_q_entry * |
|
smb2_setup_async_request(struct TCP_Server_Info *server, struct smb_rqst *rqst) |
|
{ |
|
int rc; |
|
struct smb2_sync_hdr *shdr = |
|
(struct smb2_sync_hdr *)rqst->rq_iov[0].iov_base; |
|
struct mid_q_entry *mid; |
|
|
|
if (server->tcpStatus == CifsNeedNegotiate && |
|
shdr->Command != SMB2_NEGOTIATE) |
|
return ERR_PTR(-EAGAIN); |
|
|
|
smb2_seq_num_into_buf(server, shdr); |
|
|
|
mid = smb2_mid_entry_alloc(shdr, server); |
|
if (mid == NULL) { |
|
revert_current_mid_from_hdr(server, shdr); |
|
return ERR_PTR(-ENOMEM); |
|
} |
|
|
|
rc = smb2_sign_rqst(rqst, server); |
|
if (rc) { |
|
revert_current_mid_from_hdr(server, shdr); |
|
DeleteMidQEntry(mid); |
|
return ERR_PTR(rc); |
|
} |
|
|
|
return mid; |
|
} |
|
|
|
int |
|
smb3_crypto_aead_allocate(struct TCP_Server_Info *server) |
|
{ |
|
struct crypto_aead *tfm; |
|
|
|
if (!server->secmech.ccmaesencrypt) { |
|
if ((server->cipher_type == SMB2_ENCRYPTION_AES128_GCM) || |
|
(server->cipher_type == SMB2_ENCRYPTION_AES256_GCM)) |
|
tfm = crypto_alloc_aead("gcm(aes)", 0, 0); |
|
else |
|
tfm = crypto_alloc_aead("ccm(aes)", 0, 0); |
|
if (IS_ERR(tfm)) { |
|
cifs_server_dbg(VFS, "%s: Failed alloc encrypt aead\n", |
|
__func__); |
|
return PTR_ERR(tfm); |
|
} |
|
server->secmech.ccmaesencrypt = tfm; |
|
} |
|
|
|
if (!server->secmech.ccmaesdecrypt) { |
|
if ((server->cipher_type == SMB2_ENCRYPTION_AES128_GCM) || |
|
(server->cipher_type == SMB2_ENCRYPTION_AES256_GCM)) |
|
tfm = crypto_alloc_aead("gcm(aes)", 0, 0); |
|
else |
|
tfm = crypto_alloc_aead("ccm(aes)", 0, 0); |
|
if (IS_ERR(tfm)) { |
|
crypto_free_aead(server->secmech.ccmaesencrypt); |
|
server->secmech.ccmaesencrypt = NULL; |
|
cifs_server_dbg(VFS, "%s: Failed to alloc decrypt aead\n", |
|
__func__); |
|
return PTR_ERR(tfm); |
|
} |
|
server->secmech.ccmaesdecrypt = tfm; |
|
} |
|
|
|
return 0; |
|
}
|
|
|