fs/fuse kio: fix stack overrun in request_end()

Submitted by Pavel Butsykin on May 31, 2019, 8:39 a.m.

Details

Message ID 20190531083943.28638-1-pbutsykin@virtuozzo.com
State New
Series "fs/fuse kio: fix stack overrun in request_end()"
Headers show

Commit Message

Pavel Butsykin May 31, 2019, 8:39 a.m.
Unexpectedly, request_end() function turned out to be recursive, which leads to
stack overrun:
[76293.902783]  [<ffffffffc0faa4ff>] request_end+0x334/0x3cf [fuse]
[76293.902789]  [<ffffffffc0fdc9d3>] pcs_fuse_submit+0x49d/0x4f5 [fuse_kio_pcs]
[76293.902795]  [<ffffffffc0fbd775>] ? fuse_flush_writepages+0x8f/0x8f [fuse]
[76293.902800]  [<ffffffffc0fdd745>] kpcs_req_send+0x210/0x3bb [fuse_kio_pcs]
[76293.902805]  [<ffffffffc0faa17b>] flush_bg_queue+0x1ba/0x20a [fuse]
[76293.902810]  [<ffffffffc0faa4ff>] request_end+0x334/0x3cf [fuse]
[76293.902816]  [<ffffffffc0fdc9d3>] pcs_fuse_submit+0x49d/0x4f5 [fuse_kio_pcs]
[76293.902818]  [<ffffffff8c57cdd0>] ? debug_check_no_locks_freed+0x2a0/0x2a0
[76293.902824]  [<ffffffffc0fbd775>] ? fuse_flush_writepages+0x8f/0x8f [fuse]
[76293.902829]  [<ffffffffc0fdd745>] kpcs_req_send+0x210/0x3bb [fuse_kio_pcs]
[76293.902834]  [<ffffffffc0faa17b>] flush_bg_queue+0x1ba/0x20a [fuse]
[76293.902839]  [<ffffffffc0faa4ff>] request_end+0x334/0x3cf [fuse]

To fix this, let's call request_end() inside kpcs_req_send() synchronously for
background requests.

Signed-off-by: Pavel Butsykin <pbutsykin@virtuozzo.com>
---
 fs/fuse/kio/pcs/pcs_client_types.h |  6 +++++
 fs/fuse/kio/pcs/pcs_fuse_kdirect.c | 52 ++++++++++++++++++++++++++++++++++----
 2 files changed, 53 insertions(+), 5 deletions(-)

Patch hide | download patch | download mbox

diff --git a/fs/fuse/kio/pcs/pcs_client_types.h b/fs/fuse/kio/pcs/pcs_client_types.h
index 5e72eb0fac3e..e40cd3141896 100644
--- a/fs/fuse/kio/pcs/pcs_client_types.h
+++ b/fs/fuse/kio/pcs/pcs_client_types.h
@@ -72,6 +72,12 @@  struct pcs_dentry_info {
 	struct list_head	kq;
 	spinlock_t		kq_lock;
 
+	struct {
+		struct work_struct work;
+		struct list_head   queue;
+		spinlock_t         lock;
+	} async_req_end;
+
 	struct fuse_io_cnt stat;
 };
 
diff --git a/fs/fuse/kio/pcs/pcs_fuse_kdirect.c b/fs/fuse/kio/pcs/pcs_fuse_kdirect.c
index 4dad29418e0d..bf878161bb30 100644
--- a/fs/fuse/kio/pcs/pcs_fuse_kdirect.c
+++ b/fs/fuse/kio/pcs/pcs_fuse_kdirect.c
@@ -310,7 +310,8 @@  static int fuse_pcs_kdirect_claim_op(struct fuse_conn *fc, struct file *file,
 	fuse_put_request(fc, req);
 	return err;
 }
-static void  fuse_size_grow_work(struct work_struct *w);
+static void fuse_size_grow_work(struct work_struct *w);
+static void fuse_async_reqs_finish(struct work_struct *w);
 
 static int kpcs_do_file_open(struct fuse_conn *fc, struct file *file, struct inode *inode)
 {
@@ -362,6 +363,9 @@  static int kpcs_do_file_open(struct fuse_conn *fc, struct file *file, struct ino
 		kfree(di);
 		return -ENOMEM;
 	}
+	spin_lock_init(&di->async_req_end.lock);
+	INIT_LIST_HEAD(&di->async_req_end.queue);
+	INIT_WORK(&di->async_req_end.work, fuse_async_reqs_finish);
 
 	pcs_mapping_init(&pfc->cc, &di->mapping);
 	pcs_set_fileinfo(di, &info);
@@ -428,6 +432,7 @@  void kpcs_inode_release(struct fuse_inode *fi)
 		return;
 
 	BUG_ON(!list_empty(&di->size.queue));
+	BUG_ON(!list_empty(&di->async_req_end.queue));
 	pcs_mapping_invalidate(&di->mapping);
 	pcs_mapping_deinit(&di->mapping);
 	/* TODO: properly destroy dentry info here!! */
@@ -844,6 +849,39 @@  static void _pcs_ff_free_end(struct fuse_conn *fc, struct fuse_req *req)
 		r->end(fc, req);
 }
 
+static void fuse_async_reqs_finish(struct work_struct *w)
+{
+	struct pcs_dentry_info *di = container_of(w, struct pcs_dentry_info,
+						  async_req_end.work);
+	struct fuse_conn *fc = get_fuse_conn(&di->inode->inode);
+	struct fuse_req *req, *next;
+	LIST_HEAD(pending_reqs);
+
+	spin_lock(&di->async_req_end.lock);
+	list_splice_tail_init(&di->async_req_end.queue, &pending_reqs);
+	spin_unlock(&di->async_req_end.lock);
+
+	list_for_each_entry_safe(req, next, &pending_reqs, list) {
+		list_del_init(&req->list);
+
+		TRACE("async req end: %p op:%d err:%d, di: %p\n",
+		      req, req->in.h.opcode, req->out.h.error, di);
+		request_end(fc, req);
+	}
+}
+
+static inline void request_end_queue(struct pcs_dentry_info *di,
+				     struct fuse_req *req)
+{
+	WARN_ON_ONCE(!test_bit(FR_BACKGROUND, &req->flags));
+
+	spin_lock(&di->async_req_end.lock);
+	if (list_empty(&di->async_req_end.queue))
+		queue_work(pcs_wq, &di->async_req_end.work);
+	list_add_tail(&req->list, &di->async_req_end.queue);
+	spin_unlock(&di->async_req_end.lock);
+}
+
 static bool kqueue_insert(struct pcs_dentry_info *di, struct fuse_file *ff,
 			  struct fuse_req *req)
 {
@@ -959,7 +997,7 @@  out:
 }
 
 static void pcs_fuse_submit(struct pcs_fuse_cluster *pfc, struct fuse_req *req,
-			    struct fuse_file *ff)
+			    struct fuse_file *ff, bool bg)
 {
 	struct pcs_fuse_req *r = pcs_req_from_fuse(req);
 	struct fuse_inode *fi = get_fuse_inode(req->io_inode);
@@ -1052,7 +1090,10 @@  static void pcs_fuse_submit(struct pcs_fuse_cluster *pfc, struct fuse_req *req,
 error:
 	DTRACE("do fuse_request_end req:%p op:%d err:%d\n", req, req->in.h.opcode, req->out.h.error);
 
-	request_end(pfc->fc, req);
+	if (bg)
+		request_end_queue(di, req);
+	else
+		request_end(pfc->fc, req);
 	return;
 
 submit:
@@ -1123,7 +1164,8 @@  static void _pcs_shrink_end(struct fuse_conn *fc, struct fuse_req *req)
 
 		TRACE("resubmit %p\n", &r->req);
 		list_del_init(&ireq->list);
-		pcs_fuse_submit(pfc, &r->req, r->req.ff);
+		pcs_fuse_submit(pfc, &r->req, r->req.ff,
+				test_bit(FR_BACKGROUND, &r->req.flags));
 	}
 }
 
@@ -1275,7 +1317,7 @@  static void kpcs_req_send(struct fuse_conn* fc, struct fuse_req *req,
 		atomic_inc(&req->count);
 	__clear_bit(FR_PENDING, &req->flags);
 
-	pcs_fuse_submit(pfc, req, ff ? : req->ff);
+	pcs_fuse_submit(pfc, req, ff ? : req->ff, bg);
 	if (!bg)
 		wait_event(req->waitq,
 			   test_bit(FR_FINISHED, &req->flags) && !req->end);