[net/9p]: Introduce basic flow-control for VirtIO transport.
Venkateswararao Jujjuri (JV) [Fri, 18 Mar 2011 22:49:48 +0000 (15:49 -0700)]
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 c6e1ae2..e8f046b 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);