[Devel,RHEL7,COMMIT] ms/fuse: handle only fatal signals while waiting request answer

Submitted by Konstantin Khorenko on Oct. 14, 2016, 9:49 p.m.

Details

Message ID 201610142149.u9ELn5rg027892@finist_cl7.x64_64.work.ct
State New
Series "fuse: fix signals handling while processing request"
Headers show

Commit Message

Konstantin Khorenko Oct. 14, 2016, 9:49 p.m.
The commit is pushed to "branch-rh7-3.10.0-327.36.1.vz7.19.x-ovz" and will appear at https://src.openvz.org/scm/ovz/vzkernel.git
after rh7-3.10.0-327.36.1.vz7.19.1
------>
commit 1a3de6325f248eae8dfd527617fd33a5aaeea449
Author: Stanislav Kinsburskiy <skinsbursky@virtuozzo.com>
Date:   Sat Oct 15 01:49:05 2016 +0400

    ms/fuse: handle only fatal signals while waiting request answer
    
    Patchset description:
    
    fuse: fix signals handling while processing request
    
    This patch fixes wrong SIGBUS result in page fault handler for fuse file, when
    process received a signal.
    
    Stanislav Kinsburskiy (2):
          new helper: wait_event_killable_exclusive()
          fuse: handle only fatal signals while waiting request answer
    =====================================================
    This patch description:
    
    This patch is backport of ms commit 7d3a07fcb8a0d5c06718de14fb91fdf1ef20a0e2
    ("fuse: don't mess with blocking signals").
    just use wait_event_killable{,_exclusive}().
    
    Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
    
    Although this commit loks more like cleanup, it's not.
    It fixes the issue with signals, which can lead to abort of page read in fuse,
    called from page fault, immediatly leading to SIGBUS sent to the caller.
    
    The issue is in block_sigs() implementation: it doesn't drop pending signal
    flag, which interrupts wait_event_interruptible() in request_wait_answer(),
    while it's supposed to be interrupted by SIGKILL only.
    
    IOW, any signal, arrived to the process, which does page fault handling on fuse
    file, _before_ request_wait_answer() is called, will lead to request
    interruption, producing SIGBUS error in page fault handler (filemap_fault).
    
    Once again:
    block_sigs() doesn't (!) clear TIG_SIGPENDING flag. All it does is blocking
    future signals to arrive.  Moreover, __set_task_blocked() call
    recalc_sigpending(), which check whether any of the signals to block is present
    in process pending mask, and if so - set (!) TIF_SIGPENDING on the task.
    
    IOW, any pending signal remains pending after call to blocks_sigs().
    And that's is the root of the issue.
    
    https://jira.sw.ru/browse/PSBM-53581
    
    Signed-off-by: Stanislav Kinsburskiy <skinsbursky@virtuozzo.com>
    Acked-by: Maxim Patlasov <mpatlasov@virtuozzo.com>
---
 fs/fuse/dev.c | 42 ++++++++++++++++--------------------------
 1 file changed, 16 insertions(+), 26 deletions(-)

Patch hide | download patch | download mbox

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index a0d0b1a..0b6d000 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -99,19 +99,6 @@  void fuse_request_free(struct fuse_req *req)
 	kmem_cache_free(fuse_req_cachep, req);
 }
 
-static void block_sigs(sigset_t *oldset)
-{
-	sigset_t mask;
-
-	siginitsetinv(&mask, sigmask(SIGKILL));
-	sigprocmask(SIG_BLOCK, &mask, oldset);
-}
-
-static void restore_sigs(sigset_t *oldset)
-{
-	sigprocmask(SIG_SETMASK, oldset, NULL);
-}
-
 void __fuse_get_request(struct fuse_req *req)
 {
 	atomic_inc(&req->count);
@@ -144,15 +131,9 @@  static struct fuse_req *__fuse_get_req(struct fuse_conn *fc, unsigned npages,
 	atomic_inc(&fc->num_waiting);
 
 	if (fuse_block_alloc(fc, for_background)) {
-		sigset_t oldset;
-		int intr;
-
-		block_sigs(&oldset);
-		intr = wait_event_interruptible_exclusive(fc->blocked_waitq,
-				!fuse_block_alloc(fc, for_background));
-		restore_sigs(&oldset);
 		err = -EINTR;
-		if (intr)
+		if (wait_event_killable_exclusive(fc->blocked_waitq,
+				!fuse_block_alloc(fc, for_background)))
 			goto out;
 	}
 
@@ -412,6 +393,19 @@  __acquires(fc->lock)
 	spin_lock(&fc->lock);
 }
 
+static void wait_answer_killable(struct fuse_conn *fc,
+				 struct fuse_req *req)
+__releases(fc->lock)
+__acquires(fc->lock)
+{
+	if (fatal_signal_pending(current))
+		return;
+
+	spin_unlock(&fc->lock);
+	wait_event_killable(req->waitq, req->state == FUSE_REQ_FINISHED);
+	spin_lock(&fc->lock);
+}
+
 static void queue_interrupt(struct fuse_conn *fc, struct fuse_req *req)
 {
 	list_add_tail(&req->intr_entry, &fc->interrupts);
@@ -438,12 +432,8 @@  __acquires(fc->lock)
 	}
 
 	if (!req->force) {
-		sigset_t oldset;
-
 		/* Only fatal signals may interrupt this */
-		block_sigs(&oldset);
-		wait_answer_interruptible(fc, req);
-		restore_sigs(&oldset);
+		wait_answer_killable(fc, req);
 
 		if (req->aborted)
 			goto aborted;