]> git.proxmox.com Git - mirror_ubuntu-artful-kernel.git/blobdiff - fs/cifs/smb2ops.c
Merge branches 'for-4.11/upstream-fixes', 'for-4.12/accutouch', 'for-4.12/cp2112...
[mirror_ubuntu-artful-kernel.git] / fs / cifs / smb2ops.c
index 5d456ebb381386e5299cf61123400f4985480e9a..a44b4dbe4aaec9985d40e2c39121ae5834fe1723 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"
@@ -119,7 +121,9 @@ smb2_get_credits_field(struct TCP_Server_Info *server, const int optype)
 static unsigned int
 smb2_get_credits(struct mid_q_entry *mid)
 {
-       return le16_to_cpu(((struct smb2_hdr *)mid->resp_buf)->CreditRequest);
+       struct smb2_sync_hdr *shdr = get_sync_hdr(mid->resp_buf);
+
+       return le16_to_cpu(shdr->CreditRequest);
 }
 
 static int
@@ -184,10 +188,10 @@ static struct mid_q_entry *
 smb2_find_mid(struct TCP_Server_Info *server, char *buf)
 {
        struct mid_q_entry *mid;
-       struct smb2_hdr *hdr = (struct smb2_hdr *)buf;
-       __u64 wire_mid = le64_to_cpu(hdr->MessageId);
+       struct smb2_sync_hdr *shdr = get_sync_hdr(buf);
+       __u64 wire_mid = le64_to_cpu(shdr->MessageId);
 
-       if (hdr->ProtocolId == SMB2_TRANSFORM_PROTO_NUM) {
+       if (shdr->ProtocolId == SMB2_TRANSFORM_PROTO_NUM) {
                cifs_dbg(VFS, "encrypted frame parsing not supported yet");
                return NULL;
        }
@@ -196,7 +200,7 @@ smb2_find_mid(struct TCP_Server_Info *server, char *buf)
        list_for_each_entry(mid, &server->pending_mid_q, qhead) {
                if ((mid->mid == wire_mid) &&
                    (mid->mid_state == MID_REQUEST_SUBMITTED) &&
-                   (mid->command == hdr->Command)) {
+                   (mid->command == shdr->Command)) {
                        spin_unlock(&GlobalMid_Lock);
                        return mid;
                }
@@ -209,12 +213,12 @@ static void
 smb2_dump_detail(void *buf)
 {
 #ifdef CONFIG_CIFS_DEBUG2
-       struct smb2_hdr *smb = (struct smb2_hdr *)buf;
+       struct smb2_sync_hdr *shdr = get_sync_hdr(buf);
 
        cifs_dbg(VFS, "Cmd: %d Err: 0x%x Flags: 0x%x Mid: %llu Pid: %d\n",
-                smb->Command, smb->Status, smb->Flags, smb->MessageId,
-                smb->ProcessId);
-       cifs_dbg(VFS, "smb buf %p len %u\n", smb, smb2_calc_size(smb));
+                shdr->Command, shdr->Status, shdr->Flags, shdr->MessageId,
+                shdr->ProcessId);
+       cifs_dbg(VFS, "smb buf %p len %u\n", buf, smb2_calc_size(buf));
 #endif
 }
 
@@ -1002,14 +1006,14 @@ smb2_close_dir(const unsigned int xid, struct cifs_tcon *tcon,
 static bool
 smb2_is_status_pending(char *buf, struct TCP_Server_Info *server, int length)
 {
-       struct smb2_hdr *hdr = (struct smb2_hdr *)buf;
+       struct smb2_sync_hdr *shdr = get_sync_hdr(buf);
 
-       if (hdr->Status != STATUS_PENDING)
+       if (shdr->Status != STATUS_PENDING)
                return false;
 
        if (!length) {
                spin_lock(&server->req_lock);
-               server->credits += le16_to_cpu(hdr->CreditRequest);
+               server->credits += le16_to_cpu(shdr->CreditRequest);
                spin_unlock(&server->req_lock);
                wake_up(&server->request_q);
        }
@@ -1545,6 +1549,633 @@ 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);
+}
+
+static int
+smb3_is_transform_hdr(void *buf)
+{
+       struct smb2_transform_hdr *trhdr = buf;
+
+       return trhdr->ProtocolId == SMB2_TRANSFORM_PROTO_NUM;
+}
+
+static int
+decrypt_raw_data(struct TCP_Server_Info *server, char *buf,
+                unsigned int buf_data_size, struct page **pages,
+                unsigned int npages, unsigned int page_data_size)
+{
+       struct kvec iov[2];
+       struct smb_rqst rqst = {NULL};
+       struct smb2_hdr *hdr;
+       int rc;
+
+       iov[0].iov_base = buf;
+       iov[0].iov_len = sizeof(struct smb2_transform_hdr);
+       iov[1].iov_base = buf + sizeof(struct smb2_transform_hdr);
+       iov[1].iov_len = buf_data_size;
+
+       rqst.rq_iov = iov;
+       rqst.rq_nvec = 2;
+       rqst.rq_pages = pages;
+       rqst.rq_npages = npages;
+       rqst.rq_pagesz = PAGE_SIZE;
+       rqst.rq_tailsz = (page_data_size % PAGE_SIZE) ? : PAGE_SIZE;
+
+       rc = crypt_message(server, &rqst, 0);
+       cifs_dbg(FYI, "decrypt message returned %d\n", rc);
+
+       if (rc)
+               return rc;
+
+       memmove(buf + 4, iov[1].iov_base, buf_data_size);
+       hdr = (struct smb2_hdr *)buf;
+       hdr->smb2_buf_length = cpu_to_be32(buf_data_size + page_data_size);
+       server->total_read = buf_data_size + page_data_size + 4;
+
+       return rc;
+}
+
+static int
+read_data_into_pages(struct TCP_Server_Info *server, struct page **pages,
+                    unsigned int npages, unsigned int len)
+{
+       int i;
+       int length;
+
+       for (i = 0; i < npages; i++) {
+               struct page *page = pages[i];
+               size_t n;
+
+               n = len;
+               if (len >= PAGE_SIZE) {
+                       /* enough data to fill the page */
+                       n = PAGE_SIZE;
+                       len -= n;
+               } else {
+                       zero_user(page, len, PAGE_SIZE - len);
+                       len = 0;
+               }
+               length = cifs_read_page_from_socket(server, page, n);
+               if (length < 0)
+                       return length;
+               server->total_read += length;
+       }
+
+       return 0;
+}
+
+static int
+init_read_bvec(struct page **pages, unsigned int npages, unsigned int data_size,
+              unsigned int cur_off, struct bio_vec **page_vec)
+{
+       struct bio_vec *bvec;
+       int i;
+
+       bvec = kcalloc(npages, sizeof(struct bio_vec), GFP_KERNEL);
+       if (!bvec)
+               return -ENOMEM;
+
+       for (i = 0; i < npages; i++) {
+               bvec[i].bv_page = pages[i];
+               bvec[i].bv_offset = (i == 0) ? cur_off : 0;
+               bvec[i].bv_len = min_t(unsigned int, PAGE_SIZE, data_size);
+               data_size -= bvec[i].bv_len;
+       }
+
+       if (data_size != 0) {
+               cifs_dbg(VFS, "%s: something went wrong\n", __func__);
+               kfree(bvec);
+               return -EIO;
+       }
+
+       *page_vec = bvec;
+       return 0;
+}
+
+static int
+handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
+                char *buf, unsigned int buf_len, struct page **pages,
+                unsigned int npages, unsigned int page_data_size)
+{
+       unsigned int data_offset;
+       unsigned int data_len;
+       unsigned int cur_off;
+       unsigned int cur_page_idx;
+       unsigned int pad_len;
+       struct cifs_readdata *rdata = mid->callback_data;
+       struct smb2_sync_hdr *shdr = get_sync_hdr(buf);
+       struct bio_vec *bvec = NULL;
+       struct iov_iter iter;
+       struct kvec iov;
+       int length;
+
+       if (shdr->Command != SMB2_READ) {
+               cifs_dbg(VFS, "only big read responses are supported\n");
+               return -ENOTSUPP;
+       }
+
+       if (server->ops->is_status_pending &&
+                       server->ops->is_status_pending(buf, server, 0))
+               return -1;
+
+       rdata->result = server->ops->map_error(buf, false);
+       if (rdata->result != 0) {
+               cifs_dbg(FYI, "%s: server returned error %d\n",
+                        __func__, rdata->result);
+               dequeue_mid(mid, rdata->result);
+               return 0;
+       }
+
+       data_offset = server->ops->read_data_offset(buf) + 4;
+       data_len = server->ops->read_data_length(buf);
+
+       if (data_offset < server->vals->read_rsp_size) {
+               /*
+                * win2k8 sometimes sends an offset of 0 when the read
+                * is beyond the EOF. Treat it as if the data starts just after
+                * the header.
+                */
+               cifs_dbg(FYI, "%s: data offset (%u) inside read response header\n",
+                        __func__, data_offset);
+               data_offset = server->vals->read_rsp_size;
+       } else if (data_offset > MAX_CIFS_SMALL_BUFFER_SIZE) {
+               /* data_offset is beyond the end of smallbuf */
+               cifs_dbg(FYI, "%s: data offset (%u) beyond end of smallbuf\n",
+                        __func__, data_offset);
+               rdata->result = -EIO;
+               dequeue_mid(mid, rdata->result);
+               return 0;
+       }
+
+       pad_len = data_offset - server->vals->read_rsp_size;
+
+       if (buf_len <= data_offset) {
+               /* read response payload is in pages */
+               cur_page_idx = pad_len / PAGE_SIZE;
+               cur_off = pad_len % PAGE_SIZE;
+
+               if (cur_page_idx != 0) {
+                       /* data offset is beyond the 1st page of response */
+                       cifs_dbg(FYI, "%s: data offset (%u) beyond 1st page of response\n",
+                                __func__, data_offset);
+                       rdata->result = -EIO;
+                       dequeue_mid(mid, rdata->result);
+                       return 0;
+               }
+
+               if (data_len > page_data_size - pad_len) {
+                       /* data_len is corrupt -- discard frame */
+                       rdata->result = -EIO;
+                       dequeue_mid(mid, rdata->result);
+                       return 0;
+               }
+
+               rdata->result = init_read_bvec(pages, npages, page_data_size,
+                                              cur_off, &bvec);
+               if (rdata->result != 0) {
+                       dequeue_mid(mid, rdata->result);
+                       return 0;
+               }
+
+               iov_iter_bvec(&iter, WRITE | ITER_BVEC, bvec, npages, data_len);
+       } else if (buf_len >= data_offset + data_len) {
+               /* read response payload is in buf */
+               WARN_ONCE(npages > 0, "read data can be either in buf or in pages");
+               iov.iov_base = buf + data_offset;
+               iov.iov_len = data_len;
+               iov_iter_kvec(&iter, WRITE | ITER_KVEC, &iov, 1, data_len);
+       } else {
+               /* read response payload cannot be in both buf and pages */
+               WARN_ONCE(1, "buf can not contain only a part of read data");
+               rdata->result = -EIO;
+               dequeue_mid(mid, rdata->result);
+               return 0;
+       }
+
+       /* set up first iov for signature check */
+       rdata->iov[0].iov_base = buf;
+       rdata->iov[0].iov_len = 4;
+       rdata->iov[1].iov_base = buf + 4;
+       rdata->iov[1].iov_len = server->vals->read_rsp_size - 4;
+       cifs_dbg(FYI, "0: iov_base=%p iov_len=%zu\n",
+                rdata->iov[0].iov_base, server->vals->read_rsp_size);
+
+       length = rdata->copy_into_pages(server, rdata, &iter);
+
+       kfree(bvec);
+
+       if (length < 0)
+               return length;
+
+       dequeue_mid(mid, false);
+       return length;
+}
+
+static int
+receive_encrypted_read(struct TCP_Server_Info *server, struct mid_q_entry **mid)
+{
+       char *buf = server->smallbuf;
+       struct smb2_transform_hdr *tr_hdr = (struct smb2_transform_hdr *)buf;
+       unsigned int npages;
+       struct page **pages;
+       unsigned int len;
+       unsigned int buflen = get_rfc1002_length(buf) + 4;
+       int rc;
+       int i = 0;
+
+       len = min_t(unsigned int, buflen, server->vals->read_rsp_size - 4 +
+               sizeof(struct smb2_transform_hdr)) - HEADER_SIZE(server) + 1;
+
+       rc = cifs_read_from_socket(server, buf + HEADER_SIZE(server) - 1, len);
+       if (rc < 0)
+               return rc;
+       server->total_read += rc;
+
+       len = le32_to_cpu(tr_hdr->OriginalMessageSize) + 4 -
+                                               server->vals->read_rsp_size;
+       npages = DIV_ROUND_UP(len, PAGE_SIZE);
+
+       pages = kmalloc_array(npages, sizeof(struct page *), GFP_KERNEL);
+       if (!pages) {
+               rc = -ENOMEM;
+               goto discard_data;
+       }
+
+       for (; i < npages; i++) {
+               pages[i] = alloc_page(GFP_KERNEL|__GFP_HIGHMEM);
+               if (!pages[i]) {
+                       rc = -ENOMEM;
+                       goto discard_data;
+               }
+       }
+
+       /* read read data into pages */
+       rc = read_data_into_pages(server, pages, npages, len);
+       if (rc)
+               goto free_pages;
+
+       rc = cifs_discard_remaining_data(server);
+       if (rc)
+               goto free_pages;
+
+       rc = decrypt_raw_data(server, buf, server->vals->read_rsp_size - 4,
+                             pages, npages, len);
+       if (rc)
+               goto free_pages;
+
+       *mid = smb2_find_mid(server, buf);
+       if (*mid == NULL)
+               cifs_dbg(FYI, "mid not found\n");
+       else {
+               cifs_dbg(FYI, "mid found\n");
+               (*mid)->decrypted = true;
+               rc = handle_read_data(server, *mid, buf,
+                                     server->vals->read_rsp_size,
+                                     pages, npages, len);
+       }
+
+free_pages:
+       for (i = i - 1; i >= 0; i--)
+               put_page(pages[i]);
+       kfree(pages);
+       return rc;
+discard_data:
+       cifs_discard_remaining_data(server);
+       goto free_pages;
+}
+
+static int
+receive_encrypted_standard(struct TCP_Server_Info *server,
+                          struct mid_q_entry **mid)
+{
+       int length;
+       char *buf = server->smallbuf;
+       unsigned int pdu_length = get_rfc1002_length(buf);
+       unsigned int buf_size;
+       struct mid_q_entry *mid_entry;
+
+       /* switch to large buffer if too big for a small one */
+       if (pdu_length + 4 > MAX_CIFS_SMALL_BUFFER_SIZE) {
+               server->large_buf = true;
+               memcpy(server->bigbuf, buf, server->total_read);
+               buf = server->bigbuf;
+       }
+
+       /* now read the rest */
+       length = cifs_read_from_socket(server, buf + HEADER_SIZE(server) - 1,
+                               pdu_length - HEADER_SIZE(server) + 1 + 4);
+       if (length < 0)
+               return length;
+       server->total_read += length;
+
+       buf_size = pdu_length + 4 - sizeof(struct smb2_transform_hdr);
+       length = decrypt_raw_data(server, buf, buf_size, NULL, 0, 0);
+       if (length)
+               return length;
+
+       mid_entry = smb2_find_mid(server, buf);
+       if (mid_entry == NULL)
+               cifs_dbg(FYI, "mid not found\n");
+       else {
+               cifs_dbg(FYI, "mid found\n");
+               mid_entry->decrypted = true;
+       }
+
+       *mid = mid_entry;
+
+       if (mid_entry && mid_entry->handle)
+               return mid_entry->handle(server, mid_entry);
+
+       return cifs_handle_standard(server, mid_entry);
+}
+
+static int
+smb3_receive_transform(struct TCP_Server_Info *server, struct mid_q_entry **mid)
+{
+       char *buf = server->smallbuf;
+       unsigned int pdu_length = get_rfc1002_length(buf);
+       struct smb2_transform_hdr *tr_hdr = (struct smb2_transform_hdr *)buf;
+       unsigned int orig_len = le32_to_cpu(tr_hdr->OriginalMessageSize);
+
+       if (pdu_length + 4 < sizeof(struct smb2_transform_hdr) +
+                                               sizeof(struct smb2_sync_hdr)) {
+               cifs_dbg(VFS, "Transform message is too small (%u)\n",
+                        pdu_length);
+               cifs_reconnect(server);
+               wake_up(&server->response_q);
+               return -ECONNABORTED;
+       }
+
+       if (pdu_length + 4 < orig_len + sizeof(struct smb2_transform_hdr)) {
+               cifs_dbg(VFS, "Transform message is broken\n");
+               cifs_reconnect(server);
+               wake_up(&server->response_q);
+               return -ECONNABORTED;
+       }
+
+       if (pdu_length + 4 > CIFSMaxBufSize + MAX_HEADER_SIZE(server))
+               return receive_encrypted_read(server, mid);
+
+       return receive_encrypted_standard(server, mid);
+}
+
+int
+smb3_handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid)
+{
+       char *buf = server->large_buf ? server->bigbuf : server->smallbuf;
+
+       return handle_read_data(server, mid, buf, get_rfc1002_length(buf) + 4,
+                               NULL, 0, 0);
+}
+
 struct smb_version_operations smb20_operations = {
        .compare_fids = smb2_compare_fids,
        .setup_request = smb2_setup_request,
@@ -1791,6 +2422,10 @@ 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,
+       .is_transform_hdr = smb3_is_transform_hdr,
+       .receive_transform = smb3_receive_transform,
 };
 
 #ifdef CONFIG_CIFS_SMB311
@@ -1879,6 +2514,10 @@ 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,
+       .is_transform_hdr = smb3_is_transform_hdr,
+       .receive_transform = smb3_receive_transform,
 };
 #endif /* CIFS_SMB311 */