Skip to content

Commit

Permalink
ptrace: Return the signal to continue with from ptrace_stop
Browse files Browse the repository at this point in the history
The signal a task should continue with after a ptrace stop is
inconsistently read, cleared, and sent.  Solve this by reading and
clearing the signal to be sent in ptrace_stop.

In an ideal world everything except ptrace_signal would share a common
implementation of continuing with the signal, so ptracers could count
on the signal they ask to continue with actually being delivered.  For
now retain bug compatibility and just return with the signal number
the ptracer requested the code continue with.

Link: https://lkml.kernel.org/r/875yoe7qdp.fsf_-_@email.froward.int.ebiederm.org
Reviewed-by: Kees Cook <keescook@chromium.org>
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
  • Loading branch information
Eric W. Biederman committed Mar 18, 2022
1 parent 336d4b8 commit 6487d1d
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 19 deletions.
12 changes: 6 additions & 6 deletions include/linux/ptrace.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ extern int ptrace_writedata(struct task_struct *tsk, char __user *src, unsigned
extern void ptrace_disable(struct task_struct *);
extern int ptrace_request(struct task_struct *child, long request,
unsigned long addr, unsigned long data);
extern void ptrace_notify(int exit_code, unsigned long message);
extern int ptrace_notify(int exit_code, unsigned long message);
extern void __ptrace_link(struct task_struct *child,
struct task_struct *new_parent,
const struct cred *ptracer_cred);
Expand Down Expand Up @@ -419,21 +419,21 @@ extern void sigaction_compat_abi(struct k_sigaction *act, struct k_sigaction *oa
static inline int ptrace_report_syscall(unsigned long message)
{
int ptrace = current->ptrace;
int signr;

if (!(ptrace & PT_PTRACED))
return 0;

ptrace_notify(SIGTRAP | ((ptrace & PT_TRACESYSGOOD) ? 0x80 : 0), message);
signr = ptrace_notify(SIGTRAP | ((ptrace & PT_TRACESYSGOOD) ? 0x80 : 0),
message);

/*
* this isn't the same as continuing with a signal, but it will do
* for normal use. strace only continues with a signal if the
* stopping signal is not SIGTRAP. -brl
*/
if (current->exit_code) {
send_sig(current->exit_code, current, 1);
current->exit_code = 0;
}
if (signr)
send_sig(signr, current, 1);

return fatal_signal_pending(current);
}
Expand Down
32 changes: 19 additions & 13 deletions kernel/signal.c
Original file line number Diff line number Diff line change
Expand Up @@ -2188,15 +2188,17 @@ static void do_notify_parent_cldstop(struct task_struct *tsk,
* That makes it a way to test a stopped process for
* being ptrace-stopped vs being job-control-stopped.
*
* If we actually decide not to stop at all because the tracer
* is gone, we keep current->exit_code unless clear_code.
* Returns the signal the ptracer requested the code resume
* with. If the code did not stop because the tracer is gone,
* the stop signal remains unchanged unless clear_code.
*/
static void ptrace_stop(int exit_code, int why, int clear_code,
static int ptrace_stop(int exit_code, int why, int clear_code,
unsigned long message, kernel_siginfo_t *info)
__releases(&current->sighand->siglock)
__acquires(&current->sighand->siglock)
{
bool gstop_done = false;
bool read_code = true;

if (arch_ptrace_stop_needed()) {
/*
Expand Down Expand Up @@ -2305,8 +2307,9 @@ static void ptrace_stop(int exit_code, int why, int clear_code,

/* tasklist protects us from ptrace_freeze_traced() */
__set_current_state(TASK_RUNNING);
read_code = false;
if (clear_code)
current->exit_code = 0;
exit_code = 0;
read_unlock(&tasklist_lock);
}

Expand All @@ -2316,8 +2319,11 @@ static void ptrace_stop(int exit_code, int why, int clear_code,
* any signal-sending on another CPU that wants to examine it.
*/
spin_lock_irq(&current->sighand->siglock);
if (read_code)
exit_code = current->exit_code;
current->last_siginfo = NULL;
current->ptrace_message = 0;
current->exit_code = 0;

/* LISTENING can be set only during STOP traps, clear it */
current->jobctl &= ~JOBCTL_LISTENING;
Expand All @@ -2328,9 +2334,10 @@ static void ptrace_stop(int exit_code, int why, int clear_code,
* This sets TIF_SIGPENDING, but never clears it.
*/
recalc_sigpending_tsk(current);
return exit_code;
}

static void ptrace_do_notify(int signr, int exit_code, int why, unsigned long message)
static int ptrace_do_notify(int signr, int exit_code, int why, unsigned long message)
{
kernel_siginfo_t info;

Expand All @@ -2341,18 +2348,21 @@ static void ptrace_do_notify(int signr, int exit_code, int why, unsigned long me
info.si_uid = from_kuid_munged(current_user_ns(), current_uid());

/* Let the debugger run. */
ptrace_stop(exit_code, why, 1, message, &info);
return ptrace_stop(exit_code, why, 1, message, &info);
}

void ptrace_notify(int exit_code, unsigned long message)
int ptrace_notify(int exit_code, unsigned long message)
{
int signr;

BUG_ON((exit_code & (0x7f | ~0xffff)) != SIGTRAP);
if (unlikely(task_work_pending(current)))
task_work_run();

spin_lock_irq(&current->sighand->siglock);
ptrace_do_notify(SIGTRAP, exit_code, CLD_TRAPPED, message);
signr = ptrace_do_notify(SIGTRAP, exit_code, CLD_TRAPPED, message);
spin_unlock_irq(&current->sighand->siglock);
return signr;
}

/**
Expand Down Expand Up @@ -2511,7 +2521,6 @@ static void do_jobctl_trap(void)
} else {
WARN_ON_ONCE(!signr);
ptrace_stop(signr, CLD_STOPPED, 0, 0, NULL);
current->exit_code = 0;
}
}

Expand Down Expand Up @@ -2564,15 +2573,12 @@ static int ptrace_signal(int signr, kernel_siginfo_t *info, enum pid_type type)
* comment in dequeue_signal().
*/
current->jobctl |= JOBCTL_STOP_DEQUEUED;
ptrace_stop(signr, CLD_TRAPPED, 0, 0, info);
signr = ptrace_stop(signr, CLD_TRAPPED, 0, 0, info);

/* We're back. Did the debugger cancel the sig? */
signr = current->exit_code;
if (signr == 0)
return signr;

current->exit_code = 0;

/*
* Update the siginfo structure if the signal has
* changed. If the debugger wanted something
Expand Down

0 comments on commit 6487d1d

Please sign in to comment.