[PATCH] fuse: account background requests

The previous patch removed limiting the number of outstanding requests.  This
patch adds a much simpler limiting, that is also compatible with file locking
operations.

A task may have at most one synchronous request allocated.  So these requests
need not be otherwise limited.

However the number of background requests (release, forget, asynchronous
reads, interrupted requests) can grow indefinitely.  This can be used by a
malicous user to cause FUSE to allocate arbitrary amounts of unswappable
kernel memory, denying service.

For this reason add a limit for the number of background requests, and block
allocations of new requests until the number goes bellow the limit.

Also use this mechanism to block all requests until the INIT reply is
received.

Signed-off-by: Miklos Szeredi <miklos@szeredi.hu>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 4dc104c..6c740f8 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -90,7 +90,17 @@
 
 struct fuse_req *fuse_get_req(struct fuse_conn *fc)
 {
-	struct fuse_req *req = fuse_request_alloc();
+	struct fuse_req *req;
+	sigset_t oldset;
+	int err;
+
+	block_sigs(&oldset);
+	err = wait_event_interruptible(fc->blocked_waitq, !fc->blocked);
+	restore_sigs(&oldset);
+	if (err)
+		return ERR_PTR(-EINTR);
+
+	req = fuse_request_alloc();
 	if (!req)
 		return ERR_PTR(-ENOMEM);
 
@@ -118,6 +128,11 @@
 		fput(req->file);
 	spin_lock(&fc->lock);
 	list_del(&req->bg_entry);
+	if (fc->num_background == FUSE_MAX_BACKGROUND) {
+		fc->blocked = 0;
+		wake_up_all(&fc->blocked_waitq);
+	}
+	fc->num_background--;
 	spin_unlock(&fc->lock);
 }
 
@@ -195,6 +210,9 @@
 {
 	req->background = 1;
 	list_add(&req->bg_entry, &fc->background);
+	fc->num_background++;
+	if (fc->num_background == FUSE_MAX_BACKGROUND)
+		fc->blocked = 1;
 	if (req->inode)
 		req->inode = igrab(req->inode);
 	if (req->inode2)
@@ -288,6 +306,7 @@
 static void request_send_nowait(struct fuse_conn *fc, struct fuse_req *req)
 {
 	spin_lock(&fc->lock);
+	background_request(fc, req);
 	if (fc->connected) {
 		queue_request(fc, req);
 		spin_unlock(&fc->lock);
@@ -306,9 +325,6 @@
 void request_send_background(struct fuse_conn *fc, struct fuse_req *req)
 {
 	req->isreply = 1;
-	spin_lock(&fc->lock);
-	background_request(fc, req);
-	spin_unlock(&fc->lock);
 	request_send_nowait(fc, req);
 }