[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-ID: <YRrdvKEu2JQxLI5n@zeniv-ca.linux.org.uk>
Date: Mon, 16 Aug 2021 21:50:52 +0000
From: Al Viro <viro@...iv.linux.org.uk>
To: Linus Torvalds <torvalds@...ux-foundation.org>
Cc: Eric Biederman <ebiederm@...ssion.com>,
Oleg Nesterov <oleg@...hat.com>, linux-kernel@...r.kernel.org
Subject: [PATCH][RFC] fix PTRACE_KILL
[Cc'd to security@k.o, *NOT* because I consider it a serious security hole;
it's just that the odds of catching relevant reviewers there are higher
than on l-k and there doesn't seem to be any lists where that would be
on-topic. My apologies for misuse of security@k.o ;-/]
Current implementation is racy in quite a few ways - we check that
the child is traced by us and use ptrace_resume() to feed it
SIGKILL, provided that it's still alive.
What we do not do is making sure that the victim is in ptrace stop;
as the result, it can go and violate all kinds of assumptions,
starting with "child->sighand won't change under ptrace_resume()",
"child->ptrace won't get changed under user_disable_single_step()",
etc.
Note that ptrace(2) manpage has this to say:
PTRACE_KILL
Send the tracee a SIGKILL to terminate it. (addr and data are
ignored.)
This operation is deprecated; do not use it! Instead, send a
SIGKILL directly using kill(2) or tgkill(2). The problem with
PTRACE_KILL is that it requires the tracee to be in signal-
delivery-stop, otherwise it may not work (i.e., may complete
successfully but won't kill the tracee). By contrast, sending a
SIGKILL directly has no such limitation.
So let it check (under tasklist_lock) that the victim is traced by us
and call sig_send_info() to feed it SIGKILL. It's easier that trying
to force ptrace_resume() into handling that mess and it's less brittle
that way.
Signed-off-by: Al Viro <viro@...iv.linux.org.uk>
---
diff --git a/kernel/ptrace.c b/kernel/ptrace.c
index f8589bf8d7dc..7f46be488b36 100644
--- a/kernel/ptrace.c
+++ b/kernel/ptrace.c
@@ -1220,11 +1220,6 @@ int ptrace_request(struct task_struct *child, long request,
case PTRACE_CONT:
return ptrace_resume(child, request, data);
- case PTRACE_KILL:
- if (child->exit_state) /* already dead */
- return 0;
- return ptrace_resume(child, request, SIGKILL);
-
#ifdef CONFIG_HAVE_ARCH_TRACEHOOK
case PTRACE_GETREGSET:
case PTRACE_SETREGSET: {
@@ -1270,6 +1265,20 @@ int ptrace_request(struct task_struct *child, long request,
return ret;
}
+static int ptrace_kill(struct task_struct *child)
+{
+ int ret = -ESRCH;
+
+ read_lock(&tasklist_lock);
+ if (child->ptrace && child->parent == current) {
+ if (!child->exit_state)
+ send_sig_info(SIGKILL, SEND_SIG_PRIV, child);
+ ret = 0;
+ }
+ read_unlock(&tasklist_lock);
+ return ret;
+}
+
#ifndef arch_ptrace_attach
#define arch_ptrace_attach(child) do { } while (0)
#endif
@@ -1304,8 +1313,12 @@ SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,
goto out_put_task_struct;
}
- ret = ptrace_check_attach(child, request == PTRACE_KILL ||
- request == PTRACE_INTERRUPT);
+ if (request == PTRACE_KILL) {
+ ret = ptrace_kill(child);
+ goto out_put_task_struct;
+ }
+
+ ret = ptrace_check_attach(child, request == PTRACE_INTERRUPT);
if (ret < 0)
goto out_put_task_struct;
@@ -1449,8 +1462,12 @@ COMPAT_SYSCALL_DEFINE4(ptrace, compat_long_t, request, compat_long_t, pid,
goto out_put_task_struct;
}
- ret = ptrace_check_attach(child, request == PTRACE_KILL ||
- request == PTRACE_INTERRUPT);
+ if (request == PTRACE_KILL) {
+ ret = ptrace_kill(child);
+ goto out_put_task_struct;
+ }
+
+ ret = ptrace_check_attach(child, request == PTRACE_INTERRUPT);
if (!ret) {
ret = compat_arch_ptrace(child, request, addr, data);
if (ret || request != PTRACE_DETACH)
Powered by blists - more mailing lists