diff mbox

arm: ptrace: fix syscall modification under PTRACE_O_TRACESECCOMP

Message ID 20140618202748.GA9022@www.outflux.net (mailing list archive)
State New, archived
Headers show

Commit Message

Kees Cook June 18, 2014, 8:27 p.m. UTC
An x86 tracer wanting to change the syscall uses PTRACE_SETREGS
(stored to regs->orig_ax), and an ARM tracer uses PTRACE_SET_SYSCALL
(stored to current_thread_info()->syscall). When this happens, the
syscall can change across the call to secure_computing(), since it may
block on tracer notification, and the tracer can then make changes
to the process, before we return from secure_computing(). This
means the code must respect the changed syscall after the
secure_computing() call in syscall_trace_enter(). The same is true
for tracehook_report_syscall_entry() which may also block and change
the syscall.

The x86 code handles this (it expects orig_ax to always be the
desired syscall). In the ARM case, this means we should not be touching
current_thread_info()->syscall after its initial assignment. All failures
should result in a -1 syscall, though.

Based on patch by Ricky Zhou.

Signed-off-by: Kees Cook <keescook@chromium.org>
Cc: Ricky Zhou <rickyz@google.com>
Cc: stable@vger.kernel.org
---
 arch/arm/kernel/ptrace.c |   20 +++++++++++++-------
 1 file changed, 13 insertions(+), 7 deletions(-)

Comments

Andy Lutomirski June 18, 2014, 8:51 p.m. UTC | #1
On Wed, Jun 18, 2014 at 1:27 PM, Kees Cook <keescook@chromium.org> wrote:
> An x86 tracer wanting to change the syscall uses PTRACE_SETREGS
> (stored to regs->orig_ax), and an ARM tracer uses PTRACE_SET_SYSCALL
> (stored to current_thread_info()->syscall). When this happens, the
> syscall can change across the call to secure_computing(), since it may
> block on tracer notification, and the tracer can then make changes
> to the process, before we return from secure_computing(). This
> means the code must respect the changed syscall after the
> secure_computing() call in syscall_trace_enter(). The same is true
> for tracehook_report_syscall_entry() which may also block and change
> the syscall.
>
> The x86 code handles this (it expects orig_ax to always be the
> desired syscall). In the ARM case, this means we should not be touching
> current_thread_info()->syscall after its initial assignment. All failures
> should result in a -1 syscall, though.

This looks sensible.

--Andy
Will Deacon June 20, 2014, 10:22 a.m. UTC | #2
Hi Kees,

I'm struggling to see the bug in the current code, so apologies if my
questions aren't helpful.

On Wed, Jun 18, 2014 at 09:27:48PM +0100, Kees Cook wrote:
> An x86 tracer wanting to change the syscall uses PTRACE_SETREGS
> (stored to regs->orig_ax), and an ARM tracer uses PTRACE_SET_SYSCALL
> (stored to current_thread_info()->syscall). When this happens, the
> syscall can change across the call to secure_computing(), since it may
> block on tracer notification, and the tracer can then make changes
> to the process, before we return from secure_computing(). This
> means the code must respect the changed syscall after the
> secure_computing() call in syscall_trace_enter(). The same is true
> for tracehook_report_syscall_entry() which may also block and change
> the syscall.

I don't think I understand what you mean by `the code must respect the
changed syscall'. The current code does indeed issue the new syscall, so are
you more concerned with secure_computing changing ->syscall, then the
debugger can't see the new syscall when it sees the trap from tracehook?
Are these even supposed to inter-operate?

> The x86 code handles this (it expects orig_ax to always be the
> desired syscall). In the ARM case, this means we should not be touching
> current_thread_info()->syscall after its initial assignment. All failures
> should result in a -1 syscall, though.

The only time we explicitly touch ->syscall is when we're aborting the call
(i.e. writing -1), which I think is fine.

Will
Kees Cook June 20, 2014, 4:44 p.m. UTC | #3
On Fri, Jun 20, 2014 at 3:22 AM, Will Deacon <will.deacon@arm.com> wrote:
> Hi Kees,
>
> I'm struggling to see the bug in the current code, so apologies if my
> questions aren't helpful.
>
> On Wed, Jun 18, 2014 at 09:27:48PM +0100, Kees Cook wrote:
>> An x86 tracer wanting to change the syscall uses PTRACE_SETREGS
>> (stored to regs->orig_ax), and an ARM tracer uses PTRACE_SET_SYSCALL
>> (stored to current_thread_info()->syscall). When this happens, the
>> syscall can change across the call to secure_computing(), since it may
>> block on tracer notification, and the tracer can then make changes
>> to the process, before we return from secure_computing(). This
>> means the code must respect the changed syscall after the
>> secure_computing() call in syscall_trace_enter(). The same is true
>> for tracehook_report_syscall_entry() which may also block and change
>> the syscall.
>
> I don't think I understand what you mean by `the code must respect the
> changed syscall'. The current code does indeed issue the new syscall, so are
> you more concerned with secure_computing changing ->syscall, then the
> debugger can't see the new syscall when it sees the trap from tracehook?
> Are these even supposed to inter-operate?

The problem is the use of "scno" in the call -- it results in ignoring
the value that may be set up in ->syscall by a tracer:

syscall_trace_enter(regs, scno):
    current_thread_info()->syscall = scno;
    secure_computing(scno):
        [block on ptrace]
        [ptracer changes current_thread_info()->syscall vis PTRACE_SET_SYSCALL]
    ...
    return scno;

This means the tracer's changed syscall value will be ignored
(syscall_trace_enter returns original "scno" instead of actual
current_thread_info()->syscall). In the original code, failure cases
were propagated correctly, but not tracer-induced changes.

Is that more clear? It's not an obvious state (due to the external
modification of process state during the ptrace blocking). I've also
got tests for this, if that's useful to further illustrate:

https://github.com/kees/seccomp/commit/bd24e174593f79784b97178b583f17e0ea9d2aa7

>
>> The x86 code handles this (it expects orig_ax to always be the
>> desired syscall). In the ARM case, this means we should not be touching
>> current_thread_info()->syscall after its initial assignment. All failures
>> should result in a -1 syscall, though.
>
> The only time we explicitly touch ->syscall is when we're aborting the call
> (i.e. writing -1), which I think is fine.

That was the error path, which was okay. This rearranges things to
both handle error conditions and tracer changes.

-Kees
diff mbox

Patch

diff --git a/arch/arm/kernel/ptrace.c b/arch/arm/kernel/ptrace.c
index 0dd3b79b15c3..97bd95f6aa01 100644
--- a/arch/arm/kernel/ptrace.c
+++ b/arch/arm/kernel/ptrace.c
@@ -911,6 +911,7 @@  enum ptrace_syscall_dir {
 static int tracehook_report_syscall(struct pt_regs *regs,
 				    enum ptrace_syscall_dir dir)
 {
+	int ret = 0;
 	unsigned long ip;
 
 	/*
@@ -923,30 +924,35 @@  static int tracehook_report_syscall(struct pt_regs *regs,
 	if (dir == PTRACE_SYSCALL_EXIT)
 		tracehook_report_syscall_exit(regs, 0);
 	else if (tracehook_report_syscall_entry(regs))
-		current_thread_info()->syscall = -1;
+		ret = -1;
 
 	regs->ARM_ip = ip;
-	return current_thread_info()->syscall;
+	return ret;
 }
 
 asmlinkage int syscall_trace_enter(struct pt_regs *regs, int scno)
 {
+	int ret = 0;
+
+	/* set up syscall, which may be changed in secure_computing */
 	current_thread_info()->syscall = scno;
 
 	/* Do the secure computing check first; failures should be fast. */
 	if (secure_computing(scno) == -1)
 		return -1;
 
-	if (test_thread_flag(TIF_SYSCALL_TRACE))
-		scno = tracehook_report_syscall(regs, PTRACE_SYSCALL_ENTER);
+	if (test_thread_flag(TIF_SYSCALL_TRACE) &&
+	    tracehook_report_syscall(regs, PTRACE_SYSCALL_ENTER))
+		ret = -1;
 
 	if (test_thread_flag(TIF_SYSCALL_TRACEPOINT))
-		trace_sys_enter(regs, scno);
+		trace_sys_enter(regs, current_thread_info()->syscall);
 
-	audit_syscall_entry(AUDIT_ARCH_ARM, scno, regs->ARM_r0, regs->ARM_r1,
+	audit_syscall_entry(AUDIT_ARCH_ARM, current_thread_info()->syscall,
+			    regs->ARM_r0, regs->ARM_r1,
 			    regs->ARM_r2, regs->ARM_r3);
 
-	return scno;
+	return ret ?: current_thread_info()->syscall;
 }
 
 asmlinkage void syscall_trace_exit(struct pt_regs *regs)