From: Oleg Nesterov <o...@redhat.com>

commit b5f2006144c6ae941726037120fa1001ddede784 upstream.

Commit cc731525f26a ("signal: Remove kernel interal si_code magic")
changed the value of SI_FROMUSER(SI_MESGQ), this means that mq_notify() no
longer works if the sender doesn't have rights to send a signal.

Change __do_notify() to use do_send_sig_info() instead of kill_pid_info()
to avoid check_kill_permission().

This needs the additional notify.sigev_signo != 0 check, shouldn't we
change do_mq_notify() to deny sigev_signo == 0 ?

Test-case:

        #include <signal.h>
        #include <mqueue.h>
        #include <unistd.h>
        #include <sys/wait.h>
        #include <assert.h>

        static int notified;

        static void sigh(int sig)
        {
                notified = 1;
        }

        int main(void)
        {
                signal(SIGIO, sigh);

                int fd = mq_open("/mq", O_RDWR|O_CREAT, 0666, NULL);
                assert(fd >= 0);

                struct sigevent se = {
                        .sigev_notify   = SIGEV_SIGNAL,
                        .sigev_signo    = SIGIO,
                };
                assert(mq_notify(fd, &se) == 0);

                if (!fork()) {
                        assert(setuid(1) == 0);
                        mq_send(fd, "",1,0);
                        return 0;
                }

                wait(NULL);
                mq_unlink("/mq");
                assert(notified);
                return 0;
        }

[manf...@colorfullife.com: 1) Add self_exec_id evaluation so that the 
implementation matches do_notify_parent 2) use PIDTYPE_TGID everywhere]
Fixes: cc731525f26a ("signal: Remove kernel interal si_code magic")
Reported-by: Yoji <yoji.fujihar....@gmail.com>
Signed-off-by: Oleg Nesterov <o...@redhat.com>
Signed-off-by: Manfred Spraul <manf...@colorfullife.com>
Signed-off-by: Andrew Morton <a...@linux-foundation.org>
Acked-by: "Eric W. Biederman" <ebied...@xmission.com>
Cc: Davidlohr Bueso <d...@stgolabs.net>
Cc: Markus Elfring <elfr...@users.sourceforge.net>
Cc: <1vi...@web.de>
Cc: <sta...@vger.kernel.org>
Link: 
http://lkml.kernel.org/r/e2a782e4-eab9-4f5c-c749-c07a8f7a4...@colorfullife.com
Signed-off-by: Linus Torvalds <torva...@linux-foundation.org>
Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>

---
 ipc/mqueue.c |   34 ++++++++++++++++++++++++++--------
 1 file changed, 26 insertions(+), 8 deletions(-)

--- a/ipc/mqueue.c
+++ b/ipc/mqueue.c
@@ -142,6 +142,7 @@ struct mqueue_inode_info {
 
        struct sigevent notify;
        struct pid *notify_owner;
+       u32 notify_self_exec_id;
        struct user_namespace *notify_user_ns;
        struct user_struct *user;       /* user who created, for accounting */
        struct sock *notify_sock;
@@ -774,28 +775,44 @@ static void __do_notify(struct mqueue_in
         * synchronously. */
        if (info->notify_owner &&
            info->attr.mq_curmsgs == 1) {
-               struct kernel_siginfo sig_i;
                switch (info->notify.sigev_notify) {
                case SIGEV_NONE:
                        break;
-               case SIGEV_SIGNAL:
-                       /* sends signal */
+               case SIGEV_SIGNAL: {
+                       struct kernel_siginfo sig_i;
+                       struct task_struct *task;
+
+                       /* do_mq_notify() accepts sigev_signo == 0, why?? */
+                       if (!info->notify.sigev_signo)
+                               break;
 
                        clear_siginfo(&sig_i);
                        sig_i.si_signo = info->notify.sigev_signo;
                        sig_i.si_errno = 0;
                        sig_i.si_code = SI_MESGQ;
                        sig_i.si_value = info->notify.sigev_value;
-                       /* map current pid/uid into info->owner's namespaces */
                        rcu_read_lock();
+                       /* map current pid/uid into info->owner's namespaces */
                        sig_i.si_pid = task_tgid_nr_ns(current,
                                                ns_of_pid(info->notify_owner));
-                       sig_i.si_uid = from_kuid_munged(info->notify_user_ns, 
current_uid());
+                       sig_i.si_uid = from_kuid_munged(info->notify_user_ns,
+                                               current_uid());
+                       /*
+                        * We can't use kill_pid_info(), this signal should
+                        * bypass check_kill_permission(). It is from kernel
+                        * but si_fromuser() can't know this.
+                        * We do check the self_exec_id, to avoid sending
+                        * signals to programs that don't expect them.
+                        */
+                       task = pid_task(info->notify_owner, PIDTYPE_TGID);
+                       if (task && task->self_exec_id ==
+                                               info->notify_self_exec_id) {
+                               do_send_sig_info(info->notify.sigev_signo,
+                                               &sig_i, task, PIDTYPE_TGID);
+                       }
                        rcu_read_unlock();
-
-                       kill_pid_info(info->notify.sigev_signo,
-                                     &sig_i, info->notify_owner);
                        break;
+               }
                case SIGEV_THREAD:
                        set_cookie(info->notify_cookie, NOTIFY_WOKENUP);
                        netlink_sendskb(info->notify_sock, info->notify_cookie);
@@ -1384,6 +1401,7 @@ retry:
                        info->notify.sigev_signo = notification->sigev_signo;
                        info->notify.sigev_value = notification->sigev_value;
                        info->notify.sigev_notify = SIGEV_SIGNAL;
+                       info->notify_self_exec_id = current->self_exec_id;
                        break;
                }
 


Reply via email to