]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/commitdiff
CIFS: Encrypt SMB3 requests before sending
authorPavel Shilovsky <pshilov@microsoft.com>
Thu, 3 Nov 2016 23:47:37 +0000 (16:47 -0700)
committerThadeu Lima de Souza Cascardo <cascardo@canonical.com>
Wed, 28 Jun 2017 13:18:39 +0000 (10:18 -0300)
BugLink: http://bugs.launchpad.net/bugs/1670508
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>
(backported from commit 026e93dc0a3eefb0be060bcb9ecd8d7a7fd5c398)
Signed-off-by: Joseph Salisbury <joseph.salisbury@canonical.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 aab50830a1c24cc6a2f4220fc676fa6e6e95b530..6c2508a060f3179e9c32f86ff326f7343f3ecced 100644 (file)
@@ -33,6 +33,7 @@
 #include <linux/ctype.h>
 #include <linux/random.h>
 #include <linux/highmem.h>
+#include <crypto/aead.h>
 
 static int
 cifs_crypto_shash_md5_allocate(struct TCP_Server_Info *server)
@@ -855,7 +856,7 @@ calc_seckey(struct cifs_ses *ses)
 }
 
 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);
@@ -877,6 +878,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 2961ace85e1be386aaf6ee439582fe57f4e4b273..10c8c9b8b5dfd9f0c60c5aca8daff49bb6941e8e 100644 (file)
@@ -1319,6 +1319,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 9b5c5c479bb960c5b8b51fe465a38a70a1987d89..4178da31b5e48a7346bc3a5ad74e56a5d8aedc9c 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 57ec85d62fac12961fc1b165802284fd02014396..41a66f4ef200c2513843c53adc167e70ea34218b 100644 (file)
@@ -443,7 +443,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 66c88f8742fe139ae477dba6dc6351f340069440..8c8045e9b2be574932930424292524c0edd2fe27 100644 (file)
@@ -2127,7 +2127,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);
@@ -2246,7 +2246,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 d0a05f3fac5f57cc52d35d4efcaab06973a83e28..84595cb10dc742df4823456d0b81852a4d40f93c 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"
@@ -1502,6 +1504,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,
@@ -1744,6 +1996,8 @@ struct smb_version_operations smb30_operations = {
        .wp_retry_size = smb2_wp_retry_size,
        .dir_needs_close = smb2_dir_needs_close,
        .fallocate = smb3_fallocate,
+       .init_transform_rq = smb3_init_transform_rq,
+       .free_transform_rq = smb3_free_transform_rq,
 };
 
 #ifdef CONFIG_CIFS_SMB311
@@ -1831,6 +2085,8 @@ struct smb_version_operations smb311_operations = {
        .wp_retry_size = smb2_wp_retry_size,
        .dir_needs_close = smb2_dir_needs_close,
        .fallocate = smb3_fallocate,
+       .init_transform_rq = smb3_init_transform_rq,
+       .free_transform_rq = smb3_free_transform_rq,
 };
 #endif /* CIFS_SMB311 */
 
index 31318ff0bcd2949dc42e4734db350c8d089eb7c0..8e27ae2d9169a14a6976ae7379254dc28e6b35e0 100644 (file)
@@ -132,11 +132,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;
+}