]> git.proxmox.com Git - mirror_ubuntu-zesty-kernel.git/commitdiff
CIFS: Add capability to decrypt big read responses
authorPavel Shilovsky <pshilov@microsoft.com>
Fri, 18 Nov 2016 00:20:23 +0000 (16:20 -0800)
committerTim Gardner <tim.gardner@canonical.com>
Tue, 7 Mar 2017 17:04:36 +0000 (10:04 -0700)
BugLink: http://bugs.launchpad.net/bugs/1670508
Allow to decrypt transformed packets that are bigger than the big
buffer size. In particular it is used for read responses that can
only exceed the big buffer size.

Signed-off-by: Pavel Shilovsky <pshilov@microsoft.com>
(cherry picked from commit c42a6abe3012832a68a371dabe17c2ced97e62ad)
Signed-off-by: Tim Gardner <tim.gardner@canonical.com>
fs/cifs/cifsproto.h
fs/cifs/cifssmb.c
fs/cifs/smb2ops.c

index 0cca61cb7088140ff41eeb0fd7d1f0ae1223572c..406d2c10ba78d0e53d5754fcb17d08586e534cdf 100644 (file)
@@ -77,6 +77,7 @@ extern void cifs_delete_mid(struct mid_q_entry *mid);
 extern void cifs_wake_up_task(struct mid_q_entry *mid);
 extern int cifs_handle_standard(struct TCP_Server_Info *server,
                                struct mid_q_entry *mid);
+extern int cifs_discard_remaining_data(struct TCP_Server_Info *server);
 extern int cifs_call_async(struct TCP_Server_Info *server,
                        struct smb_rqst *rqst,
                        mid_receive_t *receive, mid_callback_t *callback,
index d4b92e33d50cc513fdacbd05da9dd447e5ef88ff..f5099fb8a22f14e4c1ca2b18ff135d6b29de8e01 100644 (file)
@@ -1399,8 +1399,8 @@ openRetry:
  * Discard any remaining data in the current SMB. To do this, we borrow the
  * current bigbuf.
  */
-static int
-discard_remaining_data(struct TCP_Server_Info *server)
+int
+cifs_discard_remaining_data(struct TCP_Server_Info *server)
 {
        unsigned int rfclen = get_rfc1002_length(server->smallbuf);
        int remaining = rfclen + 4 - server->total_read;
@@ -1426,7 +1426,7 @@ cifs_readv_discard(struct TCP_Server_Info *server, struct mid_q_entry *mid)
        int length;
        struct cifs_readdata *rdata = mid->callback_data;
 
-       length = discard_remaining_data(server);
+       length = cifs_discard_remaining_data(server);
        dequeue_mid(mid, rdata->result);
        return length;
 }
@@ -1459,7 +1459,7 @@ cifs_readv_receive(struct TCP_Server_Info *server, struct mid_q_entry *mid)
 
        if (server->ops->is_status_pending &&
            server->ops->is_status_pending(buf, server, 0)) {
-               discard_remaining_data(server);
+               cifs_discard_remaining_data(server);
                return -1;
        }
 
index 989bb3175091477634f926ae04fe3784cbe9437f..a44b4dbe4aaec9985d40e2c39121ae5834fe1723 100644 (file)
@@ -1843,6 +1843,63 @@ decrypt_raw_data(struct TCP_Server_Info *server, char *buf,
        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,
@@ -1850,6 +1907,9 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
 {
        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;
@@ -1895,9 +1955,37 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
                return 0;
        }
 
+       pad_len = data_offset - server->vals->read_rsp_size;
+
        if (buf_len <= data_offset) {
                /* read response payload is in pages */
-               /* BB add code to init iter with 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");
@@ -1931,6 +2019,79 @@ handle_read_data(struct TCP_Server_Info *server, struct mid_q_entry *mid,
        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)
@@ -2000,14 +2161,8 @@ smb3_receive_transform(struct TCP_Server_Info *server, struct mid_q_entry **mid)
                return -ECONNABORTED;
        }
 
-       if (pdu_length + 4 > CIFSMaxBufSize + MAX_HEADER_SIZE(server)) {
-               cifs_dbg(VFS, "Decoding responses of big size (%u) is not supported\n",
-                        pdu_length);
-               /* BB add code to allocate and fill highmem pages here */
-               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);
 }