| From 28f4991dda2ff28285bf14f961fefe8a4e3dba45 Mon Sep 17 00:00:00 2001 |
| From: Andrei Vagin <avagin@gmail.com> |
| Date: Thu, 28 Mar 2019 20:44:13 -0700 |
| Subject: ptrace: take into account saved_sigmask in PTRACE{GET,SET}SIGMASK |
| |
| [ Upstream commit fcfc2aa0185f4a731d05a21e9f359968fdfd02e7 ] |
| |
| There are a few system calls (pselect, ppoll, etc) which replace a task |
| sigmask while they are running in a kernel-space |
| |
| When a task calls one of these syscalls, the kernel saves a current |
| sigmask in task->saved_sigmask and sets a syscall sigmask. |
| |
| On syscall-exit-stop, ptrace traps a task before restoring the |
| saved_sigmask, so PTRACE_GETSIGMASK returns the syscall sigmask and |
| PTRACE_SETSIGMASK does nothing, because its sigmask is replaced by |
| saved_sigmask, when the task returns to user-space. |
| |
| This patch fixes this problem. PTRACE_GETSIGMASK returns saved_sigmask |
| if it's set. PTRACE_SETSIGMASK drops the TIF_RESTORE_SIGMASK flag. |
| |
| Link: http://lkml.kernel.org/r/20181120060616.6043-1-avagin@gmail.com |
| Fixes: 29000caecbe8 ("ptrace: add ability to get/set signal-blocked mask") |
| Signed-off-by: Andrei Vagin <avagin@gmail.com> |
| Acked-by: Oleg Nesterov <oleg@redhat.com> |
| Cc: "Eric W. Biederman" <ebiederm@xmission.com> |
| Signed-off-by: Andrew Morton <akpm@linux-foundation.org> |
| Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org> |
| Signed-off-by: Sasha Levin (Microsoft) <sashal@kernel.org> |
| --- |
| include/linux/sched/signal.h | 18 ++++++++++++++++++ |
| kernel/ptrace.c | 15 +++++++++++++-- |
| 2 files changed, 31 insertions(+), 2 deletions(-) |
| |
| diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h |
| index fbf86ecd149d..bcaba7e8ca6e 100644 |
| --- a/include/linux/sched/signal.h |
| +++ b/include/linux/sched/signal.h |
| @@ -377,10 +377,20 @@ static inline void set_restore_sigmask(void) |
| set_thread_flag(TIF_RESTORE_SIGMASK); |
| WARN_ON(!test_thread_flag(TIF_SIGPENDING)); |
| } |
| + |
| +static inline void clear_tsk_restore_sigmask(struct task_struct *tsk) |
| +{ |
| + clear_tsk_thread_flag(tsk, TIF_RESTORE_SIGMASK); |
| +} |
| + |
| static inline void clear_restore_sigmask(void) |
| { |
| clear_thread_flag(TIF_RESTORE_SIGMASK); |
| } |
| +static inline bool test_tsk_restore_sigmask(struct task_struct *tsk) |
| +{ |
| + return test_tsk_thread_flag(tsk, TIF_RESTORE_SIGMASK); |
| +} |
| static inline bool test_restore_sigmask(void) |
| { |
| return test_thread_flag(TIF_RESTORE_SIGMASK); |
| @@ -398,6 +408,10 @@ static inline void set_restore_sigmask(void) |
| current->restore_sigmask = true; |
| WARN_ON(!test_thread_flag(TIF_SIGPENDING)); |
| } |
| +static inline void clear_tsk_restore_sigmask(struct task_struct *tsk) |
| +{ |
| + tsk->restore_sigmask = false; |
| +} |
| static inline void clear_restore_sigmask(void) |
| { |
| current->restore_sigmask = false; |
| @@ -406,6 +420,10 @@ static inline bool test_restore_sigmask(void) |
| { |
| return current->restore_sigmask; |
| } |
| +static inline bool test_tsk_restore_sigmask(struct task_struct *tsk) |
| +{ |
| + return tsk->restore_sigmask; |
| +} |
| static inline bool test_and_clear_restore_sigmask(void) |
| { |
| if (!current->restore_sigmask) |
| diff --git a/kernel/ptrace.c b/kernel/ptrace.c |
| index 84b1367935e4..f1c85b6c39ae 100644 |
| --- a/kernel/ptrace.c |
| +++ b/kernel/ptrace.c |
| @@ -29,6 +29,7 @@ |
| #include <linux/hw_breakpoint.h> |
| #include <linux/cn_proc.h> |
| #include <linux/compat.h> |
| +#include <linux/sched/signal.h> |
| |
| /* |
| * Access another process' address space via ptrace. |
| @@ -925,18 +926,26 @@ int ptrace_request(struct task_struct *child, long request, |
| ret = ptrace_setsiginfo(child, &siginfo); |
| break; |
| |
| - case PTRACE_GETSIGMASK: |
| + case PTRACE_GETSIGMASK: { |
| + sigset_t *mask; |
| + |
| if (addr != sizeof(sigset_t)) { |
| ret = -EINVAL; |
| break; |
| } |
| |
| - if (copy_to_user(datavp, &child->blocked, sizeof(sigset_t))) |
| + if (test_tsk_restore_sigmask(child)) |
| + mask = &child->saved_sigmask; |
| + else |
| + mask = &child->blocked; |
| + |
| + if (copy_to_user(datavp, mask, sizeof(sigset_t))) |
| ret = -EFAULT; |
| else |
| ret = 0; |
| |
| break; |
| + } |
| |
| case PTRACE_SETSIGMASK: { |
| sigset_t new_set; |
| @@ -962,6 +971,8 @@ int ptrace_request(struct task_struct *child, long request, |
| child->blocked = new_set; |
| spin_unlock_irq(&child->sighand->siglock); |
| |
| + clear_tsk_restore_sigmask(child); |
| + |
| ret = 0; |
| break; |
| } |
| -- |
| 2.19.1 |
| |