CIFS: Encrypt SMB3 requests before sending
authorPavel Shilovsky <pshilov@microsoft.com>
Thu, 3 Nov 2016 23:47:37 +0000 (16:47 -0700)
committerSteve French <smfrench@gmail.com>
Wed, 1 Feb 2017 22:46:36 +0000 (16:46 -0600)
This change allows to encrypt packets if it is required by a server
for SMB sessions or tree connections.

Signed-off-by: Pavel Shilovsky <pshilov@microsoft.com>
fs/cifs/Kconfig
fs/cifs/cifsencrypt.c
fs/cifs/cifsfs.c
fs/cifs/cifsglob.h
fs/cifs/cifsproto.h
fs/cifs/connect.c
fs/cifs/smb2ops.c
fs/cifs/smb2pdu.h
fs/cifs/smb2proto.h
fs/cifs/smb2transport.c

index ff0d1fe542f7440d0bb7d78b465e37654710509e..034f00f213902332816a10746560478b1e5fc16e 100644 (file)
@@ -174,6 +174,8 @@ config CIFS_SMB2
        select CRYPTO_AES
        select CRYPTO_SHA256
        select CRYPTO_CMAC
+       select CRYPTO_AEAD2
+       select CRYPTO_CCM
 
        help
          This enables support for the Server Message Block version 2
index d8af15f19dd8ca1f3506f43f0a326a48f28a2d0e..058ac9b36f0470a4d04478377cdc12a041df0db4 100644 (file)
@@ -34,6 +34,7 @@
 #include <linux/random.h>
 #include <linux/highmem.h>
 #include <crypto/skcipher.h>
+#include <crypto/aead.h>
 
 static int
 cifs_crypto_shash_md5_allocate(struct TCP_Server_Info *server)
@@ -874,7 +875,7 @@ out:
 }
 
 void
-cifs_crypto_shash_release(struct TCP_Server_Info *server)
+cifs_crypto_secmech_release(struct TCP_Server_Info *server)
 {
        if (server->secmech.cmacaes) {
                crypto_free_shash(server->secmech.cmacaes);
@@ -896,6 +897,16 @@ cifs_crypto_shash_release(struct TCP_Server_Info *server)
                server->secmech.hmacmd5 = NULL;
        }
 
+       if (server->secmech.ccmaesencrypt) {
+               crypto_free_aead(server->secmech.ccmaesencrypt);
+               server->secmech.ccmaesencrypt = NULL;
+       }
+
+       if (server->secmech.ccmaesdecrypt) {
+               crypto_free_aead(server->secmech.ccmaesdecrypt);
+               server->secmech.ccmaesdecrypt = NULL;
+       }
+
        kfree(server->secmech.sdesccmacaes);
        server->secmech.sdesccmacaes = NULL;
        kfree(server->secmech.sdeschmacsha256);
index 956dd85d7aef98a4749c7e0adff5dbee149c76bc..15e1db8738aecad0c8a86888c0fa1ada5f9b7623 100644 (file)
@@ -1376,6 +1376,8 @@ MODULE_SOFTDEP("pre: nls");
 MODULE_SOFTDEP("pre: aes");
 MODULE_SOFTDEP("pre: cmac");
 MODULE_SOFTDEP("pre: sha256");
+MODULE_SOFTDEP("pre: aead2");
+MODULE_SOFTDEP("pre: ccm");
 #endif /* CONFIG_CIFS_SMB2 */
 module_init(init_cifs)
 module_exit(exit_cifs)
index ff7114d27a6930cac14746a1faa40cfd08114dfe..14196e00f79e12279e9cc65a0ea424c94f77456c 100644 (file)
@@ -136,6 +136,8 @@ struct cifs_secmech {
        struct sdesc *sdescmd5; /* ctxt to generate cifs/smb signature */
        struct sdesc *sdeschmacsha256;  /* ctxt to generate smb2 signature */
        struct sdesc *sdesccmacaes;  /* ctxt to generate smb3 signature */
+       struct crypto_aead *ccmaesencrypt; /* smb3 encryption aead */
+       struct crypto_aead *ccmaesdecrypt; /* smb3 decryption aead */
 };
 
 /* per smb session structure/fields */
index 711718cd73dd3daf8b3fd759e9b9b8c60c82577f..26872f54ca3f92496523ab13d3d93fef267a6e37 100644 (file)
@@ -445,7 +445,7 @@ extern int SMBNTencrypt(unsigned char *, unsigned char *, unsigned char *,
                        const struct nls_table *);
 extern int setup_ntlm_response(struct cifs_ses *, const struct nls_table *);
 extern int setup_ntlmv2_rsp(struct cifs_ses *, const struct nls_table *);
-extern void cifs_crypto_shash_release(struct TCP_Server_Info *);
+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 *);
index 35ae49ed1f76e499a19cbd940a28ba28872c8c4e..b84febf960e940ec61b642fc9993e753206f02e1 100644 (file)
@@ -2154,7 +2154,7 @@ cifs_put_tcp_session(struct TCP_Server_Info *server, int from_reconnect)
        server->tcpStatus = CifsExiting;
        spin_unlock(&GlobalMid_Lock);
 
-       cifs_crypto_shash_release(server);
+       cifs_crypto_secmech_release(server);
        cifs_fscache_release_client_cookie(server);
 
        kfree(server->session_key.response);
@@ -2273,7 +2273,7 @@ cifs_get_tcp_session(struct smb_vol *volume_info)
        return tcp_ses;
 
 out_err_crypto_release:
-       cifs_crypto_shash_release(tcp_ses);
+       cifs_crypto_secmech_release(tcp_ses);
 
        put_net(cifs_net_ns(tcp_ses));
 
index ef8b2a8363b39cbaac13504f97dd8aded018bca7..54b49358eaaf91aff8854107c18e486d139778b5 100644 (file)
@@ -20,6 +20,8 @@
 #include <linux/pagemap.h>
 #include <linux/vfs.h>
 #include <linux/falloc.h>
+#include <linux/scatterlist.h>
+#include <crypto/aead.h>
 #include "cifsglob.h"
 #include "smb2pdu.h"
 #include "smb2proto.h"
@@ -1547,6 +1549,256 @@ smb2_dir_needs_close(struct cifsFileInfo *cfile)
        return !cfile->invalidHandle;
 }
 
+static void
+fill_transform_hdr(struct smb2_transform_hdr *tr_hdr, struct smb_rqst *old_rq)
+{
+       struct smb2_sync_hdr *shdr =
+                       (struct smb2_sync_hdr *)old_rq->rq_iov[1].iov_base;
+       unsigned int orig_len = get_rfc1002_length(old_rq->rq_iov[0].iov_base);
+
+       memset(tr_hdr, 0, sizeof(struct smb2_transform_hdr));
+       tr_hdr->ProtocolId = SMB2_TRANSFORM_PROTO_NUM;
+       tr_hdr->OriginalMessageSize = cpu_to_le32(orig_len);
+       tr_hdr->Flags = cpu_to_le16(0x01);
+       get_random_bytes(&tr_hdr->Nonce, SMB3_AES128CMM_NONCE);
+       memcpy(&tr_hdr->SessionId, &shdr->SessionId, 8);
+       inc_rfc1001_len(tr_hdr, sizeof(struct smb2_transform_hdr) - 4);
+       inc_rfc1001_len(tr_hdr, orig_len);
+}
+
+static struct scatterlist *
+init_sg(struct smb_rqst *rqst, u8 *sign)
+{
+       unsigned int sg_len = rqst->rq_nvec + rqst->rq_npages + 1;
+       unsigned int assoc_data_len = sizeof(struct smb2_transform_hdr) - 24;
+       struct scatterlist *sg;
+       unsigned int i;
+       unsigned int j;
+
+       sg = kmalloc_array(sg_len, sizeof(struct scatterlist), GFP_KERNEL);
+       if (!sg)
+               return NULL;
+
+       sg_init_table(sg, sg_len);
+       sg_set_buf(&sg[0], rqst->rq_iov[0].iov_base + 24, assoc_data_len);
+       for (i = 1; i < rqst->rq_nvec; i++)
+               sg_set_buf(&sg[i], rqst->rq_iov[i].iov_base,
+                                               rqst->rq_iov[i].iov_len);
+       for (j = 0; i < sg_len - 1; i++, j++) {
+               unsigned int len = (j < rqst->rq_npages - 1) ? rqst->rq_pagesz
+                                                       : rqst->rq_tailsz;
+               sg_set_page(&sg[i], rqst->rq_pages[j], len, 0);
+       }
+       sg_set_buf(&sg[sg_len - 1], sign, SMB2_SIGNATURE_SIZE);
+       return sg;
+}
+
+struct cifs_crypt_result {
+       int err;
+       struct completion completion;
+};
+
+static void cifs_crypt_complete(struct crypto_async_request *req, int err)
+{
+       struct cifs_crypt_result *res = req->data;
+
+       if (err == -EINPROGRESS)
+               return;
+
+       res->err = err;
+       complete(&res->completion);
+}
+
+/*
+ * Encrypt or decrypt @rqst message. @rqst has the following format:
+ * iov[0] - transform header (associate data),
+ * iov[1-N] and pages - data to encrypt.
+ * On success return encrypted data in iov[1-N] and pages, leave iov[0]
+ * untouched.
+ */
+static int
+crypt_message(struct TCP_Server_Info *server, struct smb_rqst *rqst, int enc)
+{
+       struct smb2_transform_hdr *tr_hdr =
+                       (struct smb2_transform_hdr *)rqst->rq_iov[0].iov_base;
+       unsigned int assoc_data_len = sizeof(struct smb2_transform_hdr) - 24;
+       struct cifs_ses *ses;
+       int rc = 0;
+       struct scatterlist *sg;
+       u8 sign[SMB2_SIGNATURE_SIZE] = {};
+       struct aead_request *req;
+       char *iv;
+       unsigned int iv_len;
+       struct cifs_crypt_result result = {0, };
+       struct crypto_aead *tfm;
+       unsigned int crypt_len = le32_to_cpu(tr_hdr->OriginalMessageSize);
+
+       init_completion(&result.completion);
+
+       ses = smb2_find_smb_ses(server, tr_hdr->SessionId);
+       if (!ses) {
+               cifs_dbg(VFS, "%s: Could not find session\n", __func__);
+               return 0;
+       }
+
+       rc = smb3_crypto_aead_allocate(server);
+       if (rc) {
+               cifs_dbg(VFS, "%s: crypto alloc failed\n", __func__);
+               return rc;
+       }
+
+       tfm = enc ? server->secmech.ccmaesencrypt :
+                                               server->secmech.ccmaesdecrypt;
+       rc = crypto_aead_setkey(tfm, enc ? ses->smb3encryptionkey :
+                               ses->smb3decryptionkey, SMB3_SIGN_KEY_SIZE);
+       if (rc) {
+               cifs_dbg(VFS, "%s: Failed to set aead key %d\n", __func__, rc);
+               return rc;
+       }
+
+       rc = crypto_aead_setauthsize(tfm, SMB2_SIGNATURE_SIZE);
+       if (rc) {
+               cifs_dbg(VFS, "%s: Failed to set authsize %d\n", __func__, rc);
+               return rc;
+       }
+
+       req = aead_request_alloc(tfm, GFP_KERNEL);
+       if (!req) {
+               cifs_dbg(VFS, "%s: Failed to alloc aead request", __func__);
+               return -ENOMEM;
+       }
+
+       if (!enc) {
+               memcpy(sign, &tr_hdr->Signature, SMB2_SIGNATURE_SIZE);
+               crypt_len += SMB2_SIGNATURE_SIZE;
+       }
+
+       sg = init_sg(rqst, sign);
+       if (!sg) {
+               cifs_dbg(VFS, "%s: Failed to init sg %d", __func__, rc);
+               goto free_req;
+       }
+
+       iv_len = crypto_aead_ivsize(tfm);
+       iv = kzalloc(iv_len, GFP_KERNEL);
+       if (!iv) {
+               cifs_dbg(VFS, "%s: Failed to alloc IV", __func__);
+               goto free_sg;
+       }
+       iv[0] = 3;
+       memcpy(iv + 1, (char *)tr_hdr->Nonce, SMB3_AES128CMM_NONCE);
+
+       aead_request_set_crypt(req, sg, sg, crypt_len, iv);
+       aead_request_set_ad(req, assoc_data_len);
+
+       aead_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+                                 cifs_crypt_complete, &result);
+
+       rc = enc ? crypto_aead_encrypt(req) : crypto_aead_decrypt(req);
+
+       if (rc == -EINPROGRESS || rc == -EBUSY) {
+               wait_for_completion(&result.completion);
+               rc = result.err;
+       }
+
+       if (!rc && enc)
+               memcpy(&tr_hdr->Signature, sign, SMB2_SIGNATURE_SIZE);
+
+       kfree(iv);
+free_sg:
+       kfree(sg);
+free_req:
+       kfree(req);
+       return rc;
+}
+
+static int
+smb3_init_transform_rq(struct TCP_Server_Info *server, struct smb_rqst *new_rq,
+                      struct smb_rqst *old_rq)
+{
+       struct kvec *iov;
+       struct page **pages;
+       struct smb2_transform_hdr *tr_hdr;
+       unsigned int npages = old_rq->rq_npages;
+       int i;
+       int rc = -ENOMEM;
+
+       pages = kmalloc_array(npages, sizeof(struct page *), GFP_KERNEL);
+       if (!pages)
+               return rc;
+
+       new_rq->rq_pages = pages;
+       new_rq->rq_npages = old_rq->rq_npages;
+       new_rq->rq_pagesz = old_rq->rq_pagesz;
+       new_rq->rq_tailsz = old_rq->rq_tailsz;
+
+       for (i = 0; i < npages; i++) {
+               pages[i] = alloc_page(GFP_KERNEL|__GFP_HIGHMEM);
+               if (!pages[i])
+                       goto err_free_pages;
+       }
+
+       iov = kmalloc_array(old_rq->rq_nvec, sizeof(struct kvec), GFP_KERNEL);
+       if (!iov)
+               goto err_free_pages;
+
+       /* copy all iovs from the old except the 1st one (rfc1002 length) */
+       memcpy(&iov[1], &old_rq->rq_iov[1],
+                               sizeof(struct kvec) * (old_rq->rq_nvec - 1));
+       new_rq->rq_iov = iov;
+       new_rq->rq_nvec = old_rq->rq_nvec;
+
+       tr_hdr = kmalloc(sizeof(struct smb2_transform_hdr), GFP_KERNEL);
+       if (!tr_hdr)
+               goto err_free_iov;
+
+       /* fill the 1st iov with a transform header */
+       fill_transform_hdr(tr_hdr, old_rq);
+       new_rq->rq_iov[0].iov_base = tr_hdr;
+       new_rq->rq_iov[0].iov_len = sizeof(struct smb2_transform_hdr);
+
+       /* copy pages form the old */
+       for (i = 0; i < npages; i++) {
+               char *dst = kmap(new_rq->rq_pages[i]);
+               char *src = kmap(old_rq->rq_pages[i]);
+               unsigned int len = (i < npages - 1) ? new_rq->rq_pagesz :
+                                                       new_rq->rq_tailsz;
+               memcpy(dst, src, len);
+               kunmap(new_rq->rq_pages[i]);
+               kunmap(old_rq->rq_pages[i]);
+       }
+
+       rc = crypt_message(server, new_rq, 1);
+       cifs_dbg(FYI, "encrypt message returned %d", rc);
+       if (rc)
+               goto err_free_tr_hdr;
+
+       return rc;
+
+err_free_tr_hdr:
+       kfree(tr_hdr);
+err_free_iov:
+       kfree(iov);
+err_free_pages:
+       for (i = i - 1; i >= 0; i--)
+               put_page(pages[i]);
+       kfree(pages);
+       return rc;
+}
+
+static void
+smb3_free_transform_rq(struct smb_rqst *rqst)
+{
+       int i = rqst->rq_npages - 1;
+
+       for (; i >= 0; i--)
+               put_page(rqst->rq_pages[i]);
+       kfree(rqst->rq_pages);
+       /* free transform header */
+       kfree(rqst->rq_iov[0].iov_base);
+       kfree(rqst->rq_iov);
+}
+
 struct smb_version_operations smb20_operations = {
        .compare_fids = smb2_compare_fids,
        .setup_request = smb2_setup_request,
@@ -1793,6 +2045,8 @@ struct smb_version_operations smb30_operations = {
        .dir_needs_close = smb2_dir_needs_close,
        .fallocate = smb3_fallocate,
        .enum_snapshots = smb3_enum_snapshots,
+       .init_transform_rq = smb3_init_transform_rq,
+       .free_transform_rq = smb3_free_transform_rq,
 };
 
 #ifdef CONFIG_CIFS_SMB311
@@ -1881,6 +2135,8 @@ struct smb_version_operations smb311_operations = {
        .dir_needs_close = smb2_dir_needs_close,
        .fallocate = smb3_fallocate,
        .enum_snapshots = smb3_enum_snapshots,
+       .init_transform_rq = smb3_init_transform_rq,
+       .free_transform_rq = smb3_free_transform_rq,
 };
 #endif /* CIFS_SMB311 */
 
index 35ff9fae1c27763e7ee089ee012c5474f38d4f2d..c03b252501a155faf6fce1b19a759ddfa8f467dd 100644 (file)
@@ -134,11 +134,14 @@ struct smb2_pdu {
        __le16 StructureSize2; /* size of wct area (varies, request specific) */
 } __packed;
 
+#define SMB3_AES128CMM_NONCE 11
+#define SMB3_AES128GCM_NONCE 12
+
 struct smb2_transform_hdr {
        __be32 smb2_buf_length; /* big endian on wire */
                                /* length is only two or three bytes - with
                                 one or two byte type preceding it that MBZ */
-       __u8   ProtocolId[4];   /* 0xFD 'S' 'M' 'B' */
+       __le32 ProtocolId;      /* 0xFD 'S' 'M' 'B' */
        __u8   Signature[16];
        __u8   Nonce[16];
        __le32 OriginalMessageSize;
index f2d511a6971b88c0019526bcd5b3938383f4f106..7d30b754e132fef9c2061ba9ef435b9c63862374 100644 (file)
@@ -56,6 +56,8 @@ extern void smb2_echo_request(struct work_struct *work);
 extern __le32 smb2_get_lease_state(struct cifsInodeInfo *cinode);
 extern bool smb2_is_valid_oplock_break(char *buffer,
                                       struct TCP_Server_Info *srv);
+extern struct cifs_ses *smb2_find_smb_ses(struct TCP_Server_Info *server,
+                                         __u64 ses_id);
 
 extern void move_smb2_info_to_cifs(FILE_ALL_INFO *dst,
                                   struct smb2_file_all_info *src);
@@ -97,6 +99,7 @@ extern int smb2_unlock_range(struct cifsFileInfo *cfile,
                             struct file_lock *flock, const unsigned int xid);
 extern int smb2_push_mandatory_locks(struct cifsFileInfo *cfile);
 extern void smb2_reconnect_server(struct work_struct *work);
+extern int smb3_crypto_aead_allocate(struct TCP_Server_Info *server);
 
 /*
  * SMB2 Worker functions - most of protocol specific implementation details
index 93b27752b634a41002794b75d4e573ad295ac152..3caa11dd957a23e3627fb0074bf9b0c1ec43a8fc 100644 (file)
@@ -31,6 +31,7 @@
 #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"
@@ -114,14 +115,14 @@ smb3_crypto_shash_allocate(struct TCP_Server_Info *server)
        return 0;
 }
 
-static struct cifs_ses *
-smb2_find_smb_ses(struct smb2_sync_hdr *shdr, struct TCP_Server_Info *server)
+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);
        list_for_each_entry(ses, &server->smb_ses_list, smb_ses_list) {
-               if (ses->Suid != shdr->SessionId)
+               if (ses->Suid != ses_id)
                        continue;
                spin_unlock(&cifs_tcp_ses_lock);
                return ses;
@@ -141,7 +142,7 @@ smb2_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
        struct smb2_sync_hdr *shdr = (struct smb2_sync_hdr *)iov[1].iov_base;
        struct cifs_ses *ses;
 
-       ses = smb2_find_smb_ses(shdr, server);
+       ses = smb2_find_smb_ses(server, shdr->SessionId);
        if (!ses) {
                cifs_dbg(VFS, "%s: Could not find session\n", __func__);
                return 0;
@@ -358,7 +359,7 @@ smb3_calc_signature(struct smb_rqst *rqst, struct TCP_Server_Info *server)
        struct smb2_sync_hdr *shdr = (struct smb2_sync_hdr *)iov[1].iov_base;
        struct cifs_ses *ses;
 
-       ses = smb2_find_smb_ses(shdr, server);
+       ses = smb2_find_smb_ses(server, shdr->SessionId);
        if (!ses) {
                cifs_dbg(VFS, "%s: Could not find session\n", __func__);
                return 0;
@@ -618,3 +619,33 @@ smb2_setup_async_request(struct TCP_Server_Info *server, struct smb_rqst *rqst)
 
        return mid;
 }
+
+int
+smb3_crypto_aead_allocate(struct TCP_Server_Info *server)
+{
+       struct crypto_aead *tfm;
+
+       if (!server->secmech.ccmaesencrypt) {
+               tfm = crypto_alloc_aead("ccm(aes)", 0, 0);
+               if (IS_ERR(tfm)) {
+                       cifs_dbg(VFS, "%s: Failed to alloc encrypt aead\n",
+                                __func__);
+                       return PTR_ERR(tfm);
+               }
+               server->secmech.ccmaesencrypt = tfm;
+       }
+
+       if (!server->secmech.ccmaesdecrypt) {
+               tfm = crypto_alloc_aead("ccm(aes)", 0, 0);
+               if (IS_ERR(tfm)) {
+                       crypto_free_aead(server->secmech.ccmaesencrypt);
+                       server->secmech.ccmaesencrypt = NULL;
+                       cifs_dbg(VFS, "%s: Failed to alloc decrypt aead\n",
+                                __func__);
+                       return PTR_ERR(tfm);
+               }
+               server->secmech.ccmaesdecrypt = tfm;
+       }
+
+       return 0;
+}