CIFS: Add capability to decrypt big read responses
authorPavel Shilovsky <pshilov@microsoft.com>
Fri, 18 Nov 2016 00:20:23 +0000 (16:20 -0800)
committerSteve French <smfrench@gmail.com>
Wed, 1 Feb 2017 22:46:37 +0000 (16:46 -0600)
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>
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);
 }