[PATCH v1] fuse: Abort waiting for a response if the daemon receives a fatal signal

From: Alexey Gladkov
Date: Thu Oct 01 2020 - 10:40:42 EST


This patch removes one kind of the deadlocks inside the fuse daemon. The
problem appear when the fuse daemon itself makes a file operation on its
filesystem and receives a fatal signal.

This deadlock can be interrupted via fusectl filesystem. But if you have
many fuse mountpoints, it will be difficult to figure out which
connection to break.

This patch aborts the connection if the fuse server receives a fatal
signal.

Reproducer: https://github.com/sargun/fuse-example
Reference: CVE-2019-20794
Fixes: 51eb01e73599 ("[PATCH] fuse: no backgrounding on interrupt")
Сс: Andrew Morton <akpm@xxxxxxxx>
Cc: Miklos Szeredi <miklos@xxxxxxxxxx>
Cc: "Eric W. Biederman" <ebiederm@xxxxxxxxxxxx>
Acked-by: "Eric W. Biederman" <ebiederm@xxxxxxxxxxxx>
Signed-off-by: Alexey Gladkov <gladkov.alexey@xxxxxxxxx>
---
fs/fuse/dev.c | 26 +++++++++++++++++++++++++-
fs/fuse/fuse_i.h | 6 ++++++
fs/fuse/inode.c | 3 +++
3 files changed, 34 insertions(+), 1 deletion(-)

diff --git a/fs/fuse/dev.c b/fs/fuse/dev.c
index 02b3c36b3676..eadfed675791 100644
--- a/fs/fuse/dev.c
+++ b/fs/fuse/dev.c
@@ -21,6 +21,7 @@
#include <linux/swap.h>
#include <linux/splice.h>
#include <linux/sched.h>
+#include <linux/fdtable.h>

MODULE_ALIAS_MISCDEV(FUSE_MINOR);
MODULE_ALIAS("devname:fuse");
@@ -357,6 +358,29 @@ static int queue_interrupt(struct fuse_iqueue *fiq, struct fuse_req *req)
return 0;
}

+static int match_fusedev(const void *p, struct file *file, unsigned fd)
+{
+ return ((struct fuse_conn *) p)->fusedev_file == file;
+}
+
+static inline bool is_fuse_daemon(struct fuse_conn *fc)
+{
+ return iterate_fd(current->files, 0, match_fusedev, fc);
+}
+
+static inline bool is_conn_untrusted(struct fuse_conn *fc)
+{
+ return (fc->sb->s_iflags & SB_I_UNTRUSTED_MOUNTER);
+}
+
+static inline bool is_event_finished(struct fuse_conn *fc, struct fuse_req *req)
+{
+ if (fc->check_fusedev_file &&
+ fatal_signal_pending(current) && is_conn_untrusted(fc) && is_fuse_daemon(fc))
+ fuse_abort_conn(fc);
+ return test_bit(FR_FINISHED, &req->flags);
+}
+
static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req)
{
struct fuse_iqueue *fiq = &fc->iq;
@@ -399,7 +423,7 @@ static void request_wait_answer(struct fuse_conn *fc, struct fuse_req *req)
* Either request is already in userspace, or it was forced.
* Wait it out.
*/
- wait_event(req->waitq, test_bit(FR_FINISHED, &req->flags));
+ wait_event(req->waitq, is_event_finished(fc, req));
}

static void __fuse_request_send(struct fuse_conn *fc, struct fuse_req *req)
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index 740a8a7d7ae6..ee9986b3c932 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -516,6 +516,9 @@ struct fuse_conn {
/** The group id for this mount */
kgid_t group_id;

+ /** The /dev/fuse file for this mount */
+ struct file *fusedev_file;
+
/** The pid namespace for this mount */
struct pid_namespace *pid_ns;

@@ -720,6 +723,9 @@ struct fuse_conn {
/* Do not show mount options */
unsigned int no_mount_options:1;

+ /** Do not check fusedev_file (virtiofs) */
+ unsigned int check_fusedev_file:1;
+
/** The number of requests waiting for completion */
atomic_t num_waiting;

diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index bba747520e9b..8dc86e5079e6 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -1201,6 +1201,8 @@ int fuse_fill_super_common(struct super_block *sb, struct fuse_fs_context *ctx)
fc->no_control = ctx->no_control;
fc->no_force_umount = ctx->no_force_umount;
fc->no_mount_options = ctx->no_mount_options;
+ fc->fusedev_file = fget(ctx->fd);
+ fc->check_fusedev_file = 1;

err = -ENOMEM;
root = fuse_get_root_inode(sb, ctx->rootmode);
@@ -1348,6 +1350,7 @@ static void fuse_sb_destroy(struct super_block *sb)

fuse_abort_conn(fc);
fuse_wait_aborted(fc);
+ fput(fc->fusedev_file);

down_write(&fc->killsb);
fc->sb = NULL;
--
2.25.4