lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <20200121185250.fk6nkcw5nqbkab5o@linux-p48b>
Date:   Tue, 21 Jan 2020 10:52:50 -0800
From:   Davidlohr Bueso <dave@...olabs.net>
To:     Alexey Mateosyan <alexey.mateosyan@...il.com>
Cc:     Andrew Morton <akpm@...ux-foundation.org>,
        Al Viro <viro@...iv.linux.org.uk>,
        Markus Elfring <elfring@...rs.sourceforge.net>,
        Li Rongqing <lirongqing@...du.com>,
        Arnd Bergmann <arnd@...db.de>,
        Kees Cook <keescook@...omium.org>,
        David Howells <dhowells@...hat.com>,
        linux-kernel@...r.kernel.org
Subject: Re: mq_notify loose notification request on fork()

On Tue, 14 Jan 2020, Alexey Mateosyan wrote:

>Reported bug on bugzilla  https://bugzilla.kernel.org/show_bug.cgi?id=206207
>Duplicating here:
>
>Bug is related to implementation of POSIX mqueues, specifically
>mq_notify syscall.\n
>
>The problem is: if we do subscription on notifications with help of
>mq_notify() syscall, and then do fork() and this fork() fail for some
>reason (for example there is pending signals for the parent process),
>then we loose the subscription on the notification.
>
>The root cause is: during copy_process() we duplicate opened file
>descriptors, and then if something goes wrong inside copy_process()
>(for example there is pending signals for the parent process) we do
>cleanup for the duplicated file descriptors via exit_files() which is
>finally calling filp_close() and flush(). From the other side, current
>implementation of ipc/mqueue.c implements file_operations.flush so
>that it removes notification for the current process, which is
>certainly not designed(desired) behavior.
>
>There is another case how to reproduce this behavior. Do fd =
>mq_open("my_queue"); mq_notify(fd, ...); and never do close(fd) to
>preserve notifications. Then if we open() and close() the queue file
>from the same process - we loose the notification request
>(subscription) for the still opened fd. I.e. current behavior is the
>first close() removes notification.

Is this a real issue? Do you have an actual reproducer? See below.

>
>If we do remove_notification() in file_ops.release instead of
>file_ops.flush in mqueue.c then this change fixes the described above
>fork() related problem. But this will change the behavior so that the
>last close() will remove the notifications.

Right, we don't want to change any user-visible behavior; and only
address the failed fork case.

I guess mqueue_flush_file() really wants to be using the result of
copy_process() task_struct pointer for its cleanup, instead of the
parent 'current'. The following does this but adds a way of getting
the pointer back from files_struct, perhaps you could test this?

Alternatively we could 'hijack' the fl_owner_t id before performing
f_op->flush() callback in filp_close() -- afaict no users actually
use the fl_owner_t field during flush callback, then we could just
restore it back to 'files' before dnotify_flush() etc paths, but I'm
not certain and is rather hacky to begin with.

Thanks,
Davidlohr

--------8<--------------------------------------------------------
diff --git a/fs/file.c b/fs/file.c
index 2f4fcf985079..70e5fe1c70d5 100644
--- a/fs/file.c
+++ b/fs/file.c
@@ -440,6 +440,7 @@ void exit_files(struct task_struct *tsk)
 
 	if (files) {
 		task_lock(tsk);
+		files->tsk = NULL;
 		tsk->files = NULL;
 		task_unlock(tsk);
 		put_files_struct(files);
diff --git a/include/linux/fdtable.h b/include/linux/fdtable.h
index f07c55ea0c22..29f906157b73 100644
--- a/include/linux/fdtable.h
+++ b/include/linux/fdtable.h
@@ -55,6 +55,7 @@ struct files_struct {
 
 	struct fdtable __rcu *fdt;
 	struct fdtable fdtab;
+	struct task_struct *tsk;
   /*
    * written part on a separate cache line in SMP
    */
diff --git a/ipc/mqueue.c b/ipc/mqueue.c
index 3d920ff15c80..22300f0fb35a 100644
--- a/ipc/mqueue.c
+++ b/ipc/mqueue.c
@@ -17,6 +17,7 @@
 #include <linux/init.h>
 #include <linux/pagemap.h>
 #include <linux/file.h>
+#include <linux/fdtable.h>
 #include <linux/mount.h>
 #include <linux/fs_context.h>
 #include <linux/namei.h>
@@ -589,10 +590,12 @@ static ssize_t mqueue_read_file(struct file *filp, char __user *u_data,
 static int mqueue_flush_file(struct file *filp, fl_owner_t id)
 {
 	struct mqueue_inode_info *info = MQUEUE_I(file_inode(filp));
+	struct files_struct *files = id;
 
 	spin_lock(&info->lock);
-	if (task_tgid(current) == info->notify_owner)
+	if (task_tgid(files->tsk) == info->notify_owner) {
 		remove_notification(info);
+	}
 
 	spin_unlock(&info->lock);
 	return 0;
diff --git a/kernel/fork.c b/kernel/fork.c
index 2508a4f238a3..8a85858db9f5 100644
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1467,6 +1467,7 @@ static int copy_files(unsigned long clone_flags, struct task_struct *tsk)
 	if (!newf)
 		goto out;
 
+	newf->tsk = tsk;
 	tsk->files = newf;
 	error = 0;
 out:

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ