[net/9p]: Introduce basic flow-control for VirtIO transport.
authorVenkateswararao Jujjuri (JV) <jvrao@linux.vnet.ibm.com>
Fri, 18 Mar 2011 22:49:48 +0000 (15:49 -0700)
committerEric Van Hensbergen <ericvh@gmail.com>
Tue, 22 Mar 2011 21:32:50 +0000 (16:32 -0500)
Recent zerocopy work in the 9P VirtIO transport maps and pins
user buffers into kernel memory for the server to work on them.
Since the user process can initiate this kind of pinning with a simple
read/write call, thousands of IO threads initiated by the user process can
hog the system resources and could result into denial of service.

This patch introduces flow control to avoid that extreme scenario.

The ceiling limit to avoid denial of service attacks is set to relatively
high (nr_free_pagecache_pages()/4) so that it won't interfere with
regular usage, but can step in extreme cases to limit the total system
hang. Since we don't have a global structure to accommodate this variable,
I choose the virtio_chan as the home for this.

Signed-off-by: Venkateswararao Jujjuri <jvrao@linux.vnet.ibm.com>
Reviewed-by: Badari Pulavarty <pbadari@us.ibm.com>
Signed-off-by: Eric Van Hensbergen <ericvh@gmail.com>
net/9p/trans_virtio.c

index c6e1ae2fb9268a3788570065ff49a9dd23014f22..e8f046b071821636daf99ae9ef79e88f28ef7a09 100644 (file)
@@ -43,6 +43,7 @@
 #include <net/9p/client.h>
 #include <net/9p/transport.h>
 #include <linux/scatterlist.h>
+#include <linux/swap.h>
 #include <linux/virtio.h>
 #include <linux/virtio_9p.h>
 #include "trans_common.h"
@@ -51,6 +52,8 @@
 
 /* a single mutex to manage channel initialization and attachment */
 static DEFINE_MUTEX(virtio_9p_lock);
+static DECLARE_WAIT_QUEUE_HEAD(vp_wq);
+static atomic_t vp_pinned = ATOMIC_INIT(0);
 
 /**
  * struct virtio_chan - per-instance transport information
@@ -78,7 +81,10 @@ struct virtio_chan {
        struct virtqueue *vq;
        int ring_bufs_avail;
        wait_queue_head_t *vc_wq;
-
+       /* This is global limit. Since we don't have a global structure,
+        * will be placing it in each channel.
+        */
+       int p9_max_pages;
        /* Scatterlist: can be too big for stack. */
        struct scatterlist sg[VIRTQUEUE_NUM];
 
@@ -159,8 +165,11 @@ static void req_done(struct virtqueue *vq)
                req = p9_tag_lookup(chan->client, rc->tag);
                if (req->tc->private) {
                        struct trans_rpage_info *rp = req->tc->private;
+                       int p = rp->rp_nr_pages;
                        /*Release pages */
                        p9_release_req_pages(rp);
+                       atomic_sub(p, &vp_pinned);
+                       wake_up(&vp_wq);
                        if (rp->rp_alloc)
                                kfree(rp);
                        req->tc->private = NULL;
@@ -269,6 +278,14 @@ p9_virtio_request(struct p9_client *client, struct p9_req_t *req)
                int rpinfo_size = sizeof(struct trans_rpage_info) +
                        sizeof(struct page *) * nr_pages;
 
+               if (atomic_read(&vp_pinned) >= chan->p9_max_pages) {
+                       err = wait_event_interruptible(vp_wq,
+                               atomic_read(&vp_pinned) < chan->p9_max_pages);
+                       if (err  == -ERESTARTSYS)
+                               return err;
+                       P9_DPRINTK(P9_DEBUG_TRANS, "9p: May gup pages now.\n");
+               }
+
                if (rpinfo_size <= (req->tc->capacity - req->tc->size)) {
                        /* We can use sdata */
                        req->tc->private = req->tc->sdata + req->tc->size;
@@ -291,6 +308,8 @@ p9_virtio_request(struct p9_client *client, struct p9_req_t *req)
                        if (rpinfo->rp_alloc)
                                kfree(rpinfo);
                        return err;
+               } else {
+                       atomic_add(rpinfo->rp_nr_pages, &vp_pinned);
                }
        }
 
@@ -452,6 +471,8 @@ static int p9_virtio_probe(struct virtio_device *vdev)
        }
        init_waitqueue_head(chan->vc_wq);
        chan->ring_bufs_avail = 1;
+       /* Ceiling limit to avoid denial of service attacks */
+       chan->p9_max_pages = nr_free_buffer_pages()/4;
 
        mutex_lock(&virtio_9p_lock);
        list_add_tail(&chan->chan_list, &virtio_chan_list);