linux/fs/smb/client/smb2transport.c
Piyush Sachdeva 5be7a0cef3 smb: client: Use FullSessionKey for AES-256 encryption key derivation
When Kerberos authentication is used with AES-256 encryption (AES-256-CCM
or AES-256-GCM), the SMB3 encryption and decryption keys must be derived
using the full session key (Session.FullSessionKey) rather than just the
first 16 bytes (Session.SessionKey).

Per MS-SMB2 section 3.2.5.3.1, when Connection.Dialect is "3.1.1" and
Connection.CipherId is AES-256-CCM or AES-256-GCM, Session.FullSessionKey
must be set to the full cryptographic key from the GSS authentication
context. The encryption and decryption key derivation (SMBC2SCipherKey,
SMBS2CCipherKey) must use this FullSessionKey as the KDF input. The
signing key derivation continues to use Session.SessionKey (first 16
bytes) in all cases.

Previously, generate_key() hardcoded SMB2_NTLMV2_SESSKEY_SIZE (16) as the
HMAC-SHA256 key input length for all derivations. When Kerberos with
AES-256 provides a 32-byte session key, the KDF for encryption/decryption
was using only the first 16 bytes, producing keys that did not match the
server's, causing mount failures with sec=krb5 and require_gcm_256=1.

Add a full_key_size parameter to generate_key() and pass the appropriate
size from generate_smb3signingkey():
 - Signing: always SMB2_NTLMV2_SESSKEY_SIZE (16 bytes)
 - Encryption/Decryption: ses->auth_key.len when AES-256, otherwise 16

Also fix cifs_dump_full_key() to report the actual session key length for
AES-256 instead of hardcoded CIFS_SESS_KEY_SIZE, so that userspace tools
like Wireshark receive the correct key for decryption.

Cc: <stable@vger.kernel.org>
Reviewed-by: Bharath SM <bharathsm@microsoft.com>
Signed-off-by: Piyush Sachdeva <psachdeva@microsoft.com>
Signed-off-by: Piyush Sachdeva <s.piyush1024@gmail.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
2026-05-07 14:09:01 -05:00

815 lines
21 KiB
C

// SPDX-License-Identifier: LGPL-2.1
/*
*
* Copyright (C) International Business Machines Corp., 2002, 2011
* Etersoft, 2012
* Author(s): Steve French (sfrench@us.ibm.com)
* Jeremy Allison (jra@samba.org) 2006
* Pavel Shilovsky (pshilovsky@samba.org) 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 <crypto/aes-cbc-macs.h>
#include <crypto/sha2.h>
#include <crypto/utils.h>
#include "cifsglob.h"
#include "cifsproto.h"
#include "smb2proto.h"
#include "cifs_debug.h"
#include "../common/smb2status.h"
#include "smb2glob.h"
static
int smb3_get_sign_key(__u64 ses_id, struct TCP_Server_Info *server, u8 *key)
{
struct cifs_chan *chan;
struct TCP_Server_Info *pserver;
struct cifs_ses *ses = NULL;
int i;
int rc = 0;
bool is_binding = false;
spin_lock(&cifs_tcp_ses_lock);
/* If server is a channel, select the primary channel */
pserver = SERVER_IS_CHAN(server) ? server->primary_server : server;
list_for_each_entry(ses, &pserver->smb_ses_list, smb_ses_list) {
if (ses->Suid == ses_id)
goto found;
}
trace_smb3_ses_not_found(ses_id);
cifs_server_dbg(FYI, "%s: Could not find session 0x%llx\n",
__func__, ses_id);
rc = -ENOENT;
goto out;
found:
spin_lock(&ses->ses_lock);
spin_lock(&ses->chan_lock);
is_binding = (cifs_chan_needs_reconnect(ses, server) &&
ses->ses_status == SES_GOOD);
if (is_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);
spin_unlock(&ses->chan_lock);
spin_unlock(&ses->ses_lock);
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);
spin_unlock(&ses->chan_lock);
spin_unlock(&ses->ses_lock);
goto out;
}
}
spin_unlock(&ses->chan_lock);
spin_unlock(&ses->ses_lock);
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 TCP_Server_Info *pserver;
struct cifs_ses *ses;
/* If server is a channel, select the primary channel */
pserver = SERVER_IS_CHAN(server) ? server->primary_server : server;
list_for_each_entry(ses, &pserver->smb_ses_list, smb_ses_list) {
if (ses->Suid != ses_id)
continue;
spin_lock(&ses->ses_lock);
if (ses->ses_status == SES_EXITING) {
spin_unlock(&ses->ses_lock);
continue;
}
cifs_smb_ses_inc_refcount(ses);
spin_unlock(&ses->ses_lock);
return ses;
}
return NULL;
}
static int smb2_get_sign_key(struct TCP_Server_Info *server,
__u64 ses_id, u8 *key)
{
struct cifs_ses *ses;
int rc = -ENOENT;
if (SERVER_IS_CHAN(server))
server = server->primary_server;
spin_lock(&cifs_tcp_ses_lock);
list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
if (ses->Suid != ses_id)
continue;
rc = 0;
spin_lock(&ses->ses_lock);
switch (ses->ses_status) {
case SES_EXITING: /* SMB2_LOGOFF */
case SES_GOOD:
if (likely(ses->auth_key.response)) {
memcpy(key, ses->auth_key.response,
SMB2_NTLMV2_SESSKEY_SIZE);
} else {
rc = smb_EIO(smb_eio_trace_no_auth_key);
}
break;
default:
rc = -EAGAIN;
break;
}
spin_unlock(&ses->ses_lock);
break;
}
spin_unlock(&cifs_tcp_ses_lock);
return rc;
}
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;
trace_smb3_tcon_ref(tcon->debug_id, tcon->tc_count,
netfs_trace_tcon_ref_get_find_sess_tcon);
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);
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;
}
static int
smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
{
int rc;
unsigned char smb2_signature[SMB2_HMACSHA256_SIZE];
struct kvec *iov = rqst->rq_iov;
struct smb2_hdr *shdr = (struct smb2_hdr *)iov[0].iov_base;
struct hmac_sha256_ctx hmac_ctx;
struct smb_rqst drqst;
__u64 sid = le64_to_cpu(shdr->SessionId);
u8 key[SMB2_NTLMV2_SESSKEY_SIZE];
rc = smb2_get_sign_key(server, sid, key);
if (unlikely(rc)) {
cifs_server_dbg(FYI, "%s: [sesid=0x%llx] couldn't find signing key: %d\n",
__func__, sid, rc);
return rc;
}
memset(smb2_signature, 0x0, SMB2_HMACSHA256_SIZE);
memset(shdr->Signature, 0x0, SMB2_SIGNATURE_SIZE);
hmac_sha256_init_usingrawkey(&hmac_ctx, key, sizeof(key));
/*
* 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) {
hmac_sha256_update(&hmac_ctx, iov[0].iov_base, iov[0].iov_len);
drqst.rq_iov++;
drqst.rq_nvec--;
}
rc = __cifs_calc_signature(
&drqst, server, smb2_signature,
&(struct cifs_calc_sig_ctx){ .hmac = &hmac_ctx });
if (!rc)
memcpy(shdr->Signature, smb2_signature, SMB2_SIGNATURE_SIZE);
return rc;
}
static void generate_key(struct cifs_ses *ses, struct kvec label,
struct kvec context, __u8 *key, unsigned int key_size,
unsigned int full_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};
unsigned char prfhash[SMB2_HMACSHA256_SIZE];
struct TCP_Server_Info *server = ses->server;
struct hmac_sha256_ctx hmac_ctx;
memset(prfhash, 0x0, SMB2_HMACSHA256_SIZE);
memset(key, 0x0, key_size);
hmac_sha256_init_usingrawkey(&hmac_ctx, ses->auth_key.response,
full_key_size);
hmac_sha256_update(&hmac_ctx, i, 4);
hmac_sha256_update(&hmac_ctx, label.iov_base, label.iov_len);
hmac_sha256_update(&hmac_ctx, &zero, 1);
hmac_sha256_update(&hmac_ctx, context.iov_base, context.iov_len);
if ((server->cipher_type == SMB2_ENCRYPTION_AES256_CCM) ||
(server->cipher_type == SMB2_ENCRYPTION_AES256_GCM)) {
hmac_sha256_update(&hmac_ctx, L256, 4);
} else {
hmac_sha256_update(&hmac_ctx, L128, 4);
}
hmac_sha256_final(&hmac_ctx, prfhash);
memcpy(key, prfhash, key_size);
}
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,
struct TCP_Server_Info *server,
const struct derivation_triplet *ptriplet)
{
unsigned int full_key_size = SMB2_NTLMV2_SESSKEY_SIZE;
bool is_binding = false;
int chan_index = 0;
spin_lock(&ses->ses_lock);
spin_lock(&ses->chan_lock);
is_binding = (cifs_chan_needs_reconnect(ses, server) &&
ses->ses_status == SES_GOOD);
chan_index = cifs_ses_get_chan_index(ses, server);
if (chan_index == CIFS_INVAL_CHAN_INDEX) {
spin_unlock(&ses->chan_lock);
spin_unlock(&ses->ses_lock);
return -EINVAL;
}
spin_unlock(&ses->chan_lock);
spin_unlock(&ses->ses_lock);
/*
* 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 (is_binding) {
generate_key(ses, ptriplet->signing.label,
ptriplet->signing.context,
ses->chans[chan_index].signkey, SMB3_SIGN_KEY_SIZE,
SMB2_NTLMV2_SESSKEY_SIZE);
} else {
generate_key(ses, ptriplet->signing.label,
ptriplet->signing.context, ses->smb3signingkey,
SMB3_SIGN_KEY_SIZE, SMB2_NTLMV2_SESSKEY_SIZE);
/*
* Per MS-SMB2 3.2.5.3.1, signing key always uses Session.SessionKey
* (first 16 bytes). Encryption/decryption keys use
* Session.FullSessionKey when dialect is 3.1.1 and cipher is
* AES-256-CCM or AES-256-GCM, otherwise Session.SessionKey.
*/
if (server->dialect == SMB311_PROT_ID &&
(server->cipher_type == SMB2_ENCRYPTION_AES256_CCM ||
server->cipher_type == SMB2_ENCRYPTION_AES256_GCM))
full_key_size = ses->auth_key.len;
/* safe to access primary channel, since it will never go away */
spin_lock(&ses->chan_lock);
memcpy(ses->chans[chan_index].signkey, ses->smb3signingkey,
SMB3_SIGN_KEY_SIZE);
spin_unlock(&ses->chan_lock);
generate_key(ses, ptriplet->encryption.label,
ptriplet->encryption.context,
ses->smb3encryptionkey, SMB3_ENC_DEC_KEY_SIZE,
full_key_size);
generate_key(ses, ptriplet->decryption.label,
ptriplet->decryption.context,
ses->smb3decryptionkey, SMB3_ENC_DEC_KEY_SIZE,
full_key_size);
}
#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",
(int)ses->auth_key.len, 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 0;
}
int
generate_smb30signingkey(struct cifs_ses *ses,
struct TCP_Server_Info *server)
{
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, server, &triplet);
}
int
generate_smb311signingkey(struct cifs_ses *ses,
struct TCP_Server_Info *server)
{
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, server, &triplet);
}
static int
smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
{
int rc;
unsigned char smb3_signature[SMB2_CMACAES_SIZE];
struct kvec *iov = rqst->rq_iov;
struct smb2_hdr *shdr = (struct smb2_hdr *)iov[0].iov_base;
struct aes_cmac_key cmac_key;
struct aes_cmac_ctx cmac_ctx;
struct smb_rqst drqst;
u8 key[SMB3_SIGN_KEY_SIZE];
if (server->vals->protocol_id <= SMB21_PROT_ID)
return smb2_calc_signature(rqst, server);
rc = smb3_get_sign_key(le64_to_cpu(shdr->SessionId), server, key);
if (unlikely(rc)) {
cifs_server_dbg(FYI, "%s: Could not get signing key\n", __func__);
return rc;
}
memset(smb3_signature, 0x0, SMB2_CMACAES_SIZE);
memset(shdr->Signature, 0x0, SMB2_SIGNATURE_SIZE);
rc = aes_cmac_preparekey(&cmac_key, key, SMB2_CMACAES_SIZE);
if (rc) {
cifs_server_dbg(VFS, "%s: Could not set key for cmac aes\n", __func__);
return rc;
}
aes_cmac_init(&cmac_ctx, &cmac_key);
/*
* 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) {
aes_cmac_update(&cmac_ctx, iov[0].iov_base, iov[0].iov_len);
drqst.rq_iov++;
drqst.rq_nvec--;
}
rc = __cifs_calc_signature(
&drqst, server, smb3_signature,
&(struct cifs_calc_sig_ctx){ .cmac = &cmac_ctx });
if (!rc)
memcpy(shdr->Signature, smb3_signature, SMB2_SIGNATURE_SIZE);
return rc;
}
/* must be called with server->srv_mutex held */
static int
smb2_sign_rqst(struct smb_rqst *rqst, struct TCP_Server_Info *server)
{
struct smb2_hdr *shdr;
struct smb2_sess_setup_req *ssr;
bool is_binding;
bool is_signed;
shdr = (struct smb2_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;
spin_lock(&server->srv_lock);
if (server->ops->need_neg &&
server->ops->need_neg(server)) {
spin_unlock(&server->srv_lock);
return 0;
}
spin_unlock(&server->srv_lock);
if (!is_binding && !server->session_estab) {
strscpy(shdr->Signature, "BSRSPYL");
return 0;
}
return smb3_calc_signature(rqst, server);
}
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_hdr *shdr =
(struct smb2_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 original 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 = smb3_calc_signature(rqst, server);
if (rc)
return rc;
if (crypto_memneq(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_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_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_pool, GFP_NOFS);
memset(temp, 0, sizeof(struct mid_q_entry));
refcount_set(&temp->refcount, 1);
spin_lock_init(&temp->mid_lock);
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;
/*
* 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(&mid_count);
temp->mid_state = MID_REQUEST_ALLOCATED;
trace_smb3_cmd_enter(le32_to_cpu(shdr->Id.SyncId.TreeId),
le64_to_cpu(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_hdr *shdr, struct mid_q_entry **mid)
{
switch (READ_ONCE(server->tcpStatus)) {
case CifsExiting:
return -ENOENT;
case CifsNeedReconnect:
cifs_dbg(FYI, "tcp session dead - return to caller to retry\n");
return -EAGAIN;
case CifsNeedNegotiate:
if (shdr->Command != SMB2_NEGOTIATE)
return -EAGAIN;
break;
default:
break;
}
switch (READ_ONCE(ses->ses_status)) {
case SES_NEW:
if (shdr->Command != SMB2_SESSION_SETUP &&
shdr->Command != SMB2_NEGOTIATE)
return -EAGAIN;
/* else ok - we are setting up session */
break;
case SES_EXITING:
if (shdr->Command != SMB2_LOGOFF)
return -EAGAIN;
/* else ok - we are shutting down the session */
break;
default:
break;
}
*mid = smb2_mid_entry_alloc(shdr, server);
if (*mid == NULL)
return -ENOMEM;
spin_lock(&server->mid_queue_lock);
list_add_tail(&(*mid)->qhead, &server->pending_mid_q);
spin_unlock(&server->mid_queue_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_hdr *shdr =
(struct smb2_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);
delete_mid(server, 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_hdr *shdr =
(struct smb2_hdr *)rqst->rq_iov[0].iov_base;
struct mid_q_entry *mid;
spin_lock(&server->srv_lock);
if (server->tcpStatus == CifsNeedNegotiate &&
shdr->Command != SMB2_NEGOTIATE) {
spin_unlock(&server->srv_lock);
return ERR_PTR(-EAGAIN);
}
spin_unlock(&server->srv_lock);
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);
release_mid(server, mid);
return ERR_PTR(rc);
}
return mid;
}
int
smb3_crypto_aead_allocate(struct TCP_Server_Info *server)
{
struct crypto_aead *tfm;
if (!server->secmech.enc) {
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.enc = tfm;
}
if (!server->secmech.dec) {
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.enc);
server->secmech.enc = NULL;
cifs_server_dbg(VFS, "%s: Failed to alloc decrypt aead\n",
__func__);
return PTR_ERR(tfm);
}
server->secmech.dec = tfm;
}
return 0;
}