wait/ptrace: always assume __WALL if the child is traced
Oleg Nesterov
maintainercover wait/ptrace: always assume __WALL if the child is traced
Damn. I simply do not know what should/can we do. From the change log:
And I can only hope that this won't break something.
yet this patch cc's -stable.
Please see the changelog, but in short: this is not a kernel bug but unlikely we can fix all distributions, so I think we have to change the kernel.
HOWEVER. With this change __WCLONE and __WALL have no effect for debugger, do_wait() works as if __WALL is set if the child (natural or not) is traced.
Jan, Pedro, could you please confirm this won't break gdb? I tried to look into gdb-7.1, and at first glance gdb uses __WCLONE only because __WALL doesn't work on older kernels, iow it seems to me that gdb actually wants __WALL so this change should be fine.
Any other ideas?
Oleg.
Oleg Nesterov
maintainerPATCH 1/2 wait/ptrace: always assume __WALL if the child is traced
The following program (simplified version of generated by syzkaller)
#include <pthread.h>
#include <unistd.h>
#include <sys/ptrace.h>
#include <stdio.h>
#include <signal.h>
void *thread_func(void *arg)
{
ptrace(PTRACE_TRACEME, 0,0,0);
return 0;
}
int main(void)
{
pthread_t thread;
if (fork())
return 0;
while (getppid() != 1)
;
pthread_create(&thread, NULL, thread_func, NULL);
pthread_join(thread, NULL);
return 0;
}creates the unreapable zombie if /sbin/init doesn't use __WALL.
This is not a kernel bug, at least in a sense that everything works as
expected: debugger should reap a traced sub-thread before it can reap the leader, but without __WALL/__WCLONE do_wait() ignores sub-threads.
Unfortunately, it seems that /sbin/init in most (all?) distributions doesn't use it and we have to change the kernel to avoid the problem.
This patch just adds the "ptrace" check into eligible_child(). To some degree this matches the "tsk->ptrace" in exit_notify(), ->exit_signal is mostly ignored when the tracee reports to debugger.
This obviously means the user-visible change: __WCLONE and __WALL no longer have any meaning for debugger. And I can only hope that this won't break something.
We could make a more conservative change. Say, we can take __WCLONE into account, or !thread_group_leader(). But it would be nice to not complicate these historical/confusing checks.
--- kernel/exit.c | 29 ++++++++++++++++++++--------- 1 files changed, 20 insertions(+), 9 deletions(-)
kernel/exit.c +20 -9
@@ -914,17 +914,28 @@ static int eligible_pid(struct wait_opts *wo, struct task_struct *p)task_pid_type (p, wo->wo_type) == wo->wo_pid; } -static int eligible_child (struct wait_opts *wo,struct task_struct *p) +static int +eligible_child (struct wait_opts *wo,bool ptrace,struct task_struct *p) {if (!eligible_pid (wo, p))return 0 ; -/* Wait for all children (clone and not) if __WALL is set; -* otherwise, wait for clone children *only* if __WCLONE is -* set; otherwise, wait for non-clone children *only*. (Note: -* A "clone" child here is one that reports to its parent -* using a signal other than SIGCHLD.) */ -if (((p->exit_signal !=SIGCHLD ) ^ !!(wo->wo_flags & __WCLONE)) - && !(wo->wo_flags & __WALL)) + +/* +* Wait for all children (clone and not) if __WALL is set or +* if it is traced by us. +*/ +if (ptrace || (wo->wo_flags & __WALL)) +return 1 ; + +/* +* Otherwise, wait for clone children *only* if __WCLONE is set; +* otherwise, wait for non-clone children *only*. +* +* Note: a "clone" child here is one that reports to its parent +* using a signal other than SIGCHLD, or a non-leader thread which +* we can only see if it is traced by us. +*/ +if ((p->exit_signal !=SIGCHLD ) ^ !!(wo->wo_flags & __WCLONE))return 0 ;return 1 ;
@@ -1297,7 +1308,7 @@ static int wait_consider_task(struct wait_opts *wo, int ptrace,if (unlikely (exit_state ==EXIT_DEAD ))return 0 ; - ret =eligible_child (wo, p); + ret =eligible_child (wo, ptrace, p);if (!ret)return ret;
Signature · 1 line
1.5.5.1
Quoted 29 lines · On Tue, 20 Oct 2015 19:17:54 +0200 Oleg Nesterov <[email protected]> wrote:
The following program (simplified version of generated by syzkaller)
#include <pthread.h> #include <unistd.h> #include <sys/ptrace.h> #include <stdio.h> #include <signal.h> void *thread_func(void *arg) { ptrace(PTRACE_TRACEME, 0,0,0); return 0; } int main(void) { pthread_t thread; if (fork()) return 0; while (getppid() != 1) ; pthread_create(&thread, NULL, thread_func, NULL); pthread_join(thread, NULL); return 0; }
Quoted 8 lines
creates the unreapable zombie if
/sbin/initdoesn't use__WALL.This is not a kernel bug, at least in a sense that everything works as
expected: debugger should reap a traced sub-thread before it can reap the leader, but without __WALL/__WCLONEdo_wait()ignores sub-threads.Unfortunately, it seems that
/sbin/initin most (all?) distributions doesn't use it and we have to change the kernel to avoid the problem.
Well, to fix this a distro needs to roll out a new kernel. Or a new init(8). Is there any reason to believe that distributing/deploying a new kernel is significantly easier for everyone? Because fixing init sounds like a much preferable solution to this problem.
Quoted 37 lines · Andrew Morton
Quoted 35 lines · On Tue, 20 Oct 2015 19:17:54 +0200 Oleg Nesterov <[email protected]> wrote:
The following program (simplified version of generated by syzkaller)
#include <pthread.h> #include <unistd.h> #include <sys/ptrace.h> #include <stdio.h> #include <signal.h> void *thread_func(void *arg) { ptrace(PTRACE_TRACEME, 0,0,0); return 0; } int main(void) { pthread_t thread; if (fork()) return 0; while (getppid() != 1) ; pthread_create(&thread, NULL, thread_func, NULL); pthread_join(thread, NULL); return 0; }creates the unreapable zombie if
/sbin/initdoesn't use__WALL.This is not a kernel bug, at least in a sense that everything works as
expected: debugger should reap a traced sub-thread before it can reap the leader, but without __WALL/__WCLONEdo_wait()ignores sub-threads.
Quoted 7 lines
Quoted 2 lines
Unfortunately, it seems that
/sbin/initin most (all?) distributions doesn't use it and we have to change the kernel to avoid the problem.Well, to fix this a distro needs to roll out a new kernel. Or a new init(8). Is there any reason to believe that distributing/deploying a new kernel is significantly easier for everyone? Because fixing init sounds like a much preferable solution to this problem.
Patched kernel allows to run obsoleted distro inside containers.
Oleg Nesterov
maintainerQuoted 38 lines · Andrew Morton
Quoted 35 lines · On Tue, 20 Oct 2015 19:17:54 +0200 Oleg Nesterov <[email protected]> wrote:
The following program (simplified version of generated by syzkaller)
#include <pthread.h> #include <unistd.h> #include <sys/ptrace.h> #include <stdio.h> #include <signal.h> void *thread_func(void *arg) { ptrace(PTRACE_TRACEME, 0,0,0); return 0; } int main(void) { pthread_t thread; if (fork()) return 0; while (getppid() != 1) ; pthread_create(&thread, NULL, thread_func, NULL); pthread_join(thread, NULL); return 0; }creates the unreapable zombie if
/sbin/initdoesn't use__WALL.This is not a kernel bug, at least in a sense that everything works as
expected: debugger should reap a traced sub-thread before it can reap the leader, but without __WALL/__WCLONEdo_wait()ignores sub-threads.
Quoted 7 lines
Quoted 2 lines
Unfortunately, it seems that
/sbin/initin most (all?) distributions doesn't use it and we have to change the kernel to avoid the problem.Well, to fix this a distro needs to roll out a new kernel. Or a new init(8). Is there any reason to believe that distributing/deploying a new kernel is significantly easier for everyone? Because fixing init sounds like a much preferable solution to this problem.
I will be happy if we decide that this is userpace problem and we should not fix the kernel. I simply do not know.
However, please look at 2/2 which imho makes sense regardless and looks "obviously safe". Without this patch waitid() can not use __WALL, so if /sbin/init uses waitid() then the userspace fix won't be one-liner. And at least Fedora22 and Ubuntu use waitid().
So personally I'd prefer 2/2 + fix-init, not sure if this can work...
Oleg.
Quoted 42 lines · On Wed, 21 Oct 2015 19:41:50 +0200 Oleg Nesterov <[email protected]> wrote:
Quoted 41 lines · Andrew Morton
Quoted 37 lines · On Tue, 20 Oct 2015 19:17:54 +0200 Oleg Nesterov <[email protected]> wrote:
The following program (simplified version of generated by syzkaller)
#include <pthread.h> #include <unistd.h> #include <sys/ptrace.h> #include <stdio.h> #include <signal.h> void *thread_func(void *arg) { ptrace(PTRACE_TRACEME, 0,0,0); return 0; } int main(void) { pthread_t thread; if (fork()) return 0; while (getppid() != 1) ; pthread_create(&thread, NULL, thread_func, NULL); pthread_join(thread, NULL); return 0; }creates the unreapable zombie if
/sbin/initdoesn't use__WALL.This is not a kernel bug, at least in a sense that everything works as
expected: debugger should reap a traced sub-thread before it can reap the leader, but without __WALL/__WCLONEdo_wait()ignores sub-threads.Unfortunately, it seems that
/sbin/initin most (all?) distributions doesn't use it and we have to change the kernel to avoid the problem.
Quoted 7 lines
Quoted 4 lines
Well, to fix this a distro needs to roll out a new kernel. Or a new init(8). Is there any reason to believe that distributing/deploying a new kernel is significantly easier for everyone? Because fixing init sounds like a much preferable solution to this problem.
I will be happy if we decide that this is userpace problem and we should not fix the kernel. I simply do not know.
The kernel patch sounds pretty sketchy - something we should avoid doing if at all possible.
Quoted 4 lines
However, please look at 2/2 which imho makes sense regardless and looks "obviously safe". Without this patch
waitid()can not use__WALL, so if/sbin/inituseswaitid()then the userspace fix won't be one-liner. And at least Fedora22 and Ubuntu usewaitid().
2/2 does look sensible (needs a better changelog if it's to be a standalone thing), but if we're expecting distros to fix this with an updated init(8) only, then they can't assume that the kernel's waitid() has been altered. So init will need the not-one-liner version of the fix.
Quoted 1 line
So personally I'd prefer 2/2 + fix-init, not sure if this can work...
I'm just guessing here. Are you (or someone) able to find out which approach the distros will prefer, and why?
And what has to be done to init(8) to fix this bug when running current kernels?
Oleg Nesterov
maintainerQuoted 19 lines · Andrew Morton
Quoted 16 lines · On Wed, 21 Oct 2015 19:41:50 +0200 Oleg Nesterov <[email protected]> wrote:
Quoted 14 lines · Andrew Morton
Quoted 6 lines · On Tue, 20 Oct 2015 19:17:54 +0200 Oleg Nesterov <[email protected]> wrote:
This is not a kernel bug, at least in a sense that everything works as
expected: debugger should reap a traced sub-thread before it can reap the leader, but without __WALL/__WCLONEdo_wait()ignores sub-threads.Unfortunately, it seems that
/sbin/initin most (all?) distributions doesn't use it and we have to change the kernel to avoid the problem.Well, to fix this a distro needs to roll out a new kernel. Or a new init(8). Is there any reason to believe that distributing/deploying a new kernel is significantly easier for everyone? Because fixing init sounds like a much preferable solution to this problem.
Quoted 5 lines
Quoted 2 lines
I will be happy if we decide that this is userpace problem and we should not fix the kernel. I simply do not know.
The kernel patch sounds pretty sketchy - something we should avoid doing if at all possible.
Yes, I agree.
Quoted 7 lines
Quoted 4 lines
However, please look at 2/2 which imho makes sense regardless and looks "obviously safe". Without this patch
waitid()can not use__WALL, so if/sbin/inituseswaitid()then the userspace fix won't be one-liner. And at least Fedora22 and Ubuntu usewaitid().2/2 does look sensible (needs a better changelog if it's to be a standalone thing),
Yes. Without 1/2 the changlelog should menetion that at least __WALL makes sense because /sbin/init has a good reason to use waitid(WALL).
Plus it should cc -stable.
Quoted 3 lines
but if we're expecting distros to fix this with an
updated init(8) only, then they can't assume that the kernel'swaitid()has been altered.
Well, 2/2 looks safe for every kernel version... starting from git history at least.
Quoted 2 lines
So init will need the not-one-liner version of the fix.
Then I think this fix will stay forever ;)
Quoted 4 lines
Quoted 1 line
So personally I'd prefer 2/2 + fix-init, not sure if this can work...
I'm just guessing here. Are you (or someone) able to find out which approach the distros will prefer, and why?
No, I have no idea, sorry.
Quoted 2 lines
And what has to be done to init(8) to fix this bug when running current kernels?
Say, http://git.busybox.net/busybox/tree/init/init.c
at first glance it just needs
- wpid = waitpid(-1, NULL, maybe_WNOHANG); + wpid = waitpid(-1, NULL, maybe_WNOHANG | __WALL);
I have a testing machine running Fedora22, according to strace /bin/systemd does
waitid(P_ALL, 0, {}, WNOHANG|WEXITED|WNOWAIT, NULL);
...
waitid(P_PID, 25558, {INFO}, WEXITED, NULL)so it probably wants siginfo and thus it can't use waitpid. Without 2/2 systemd can probably just do something like
while (waitpid(-1, NULL, __WCLONE | WNOHANG) != ESRCH) {
log("Dmitry Vyukov detected");
}every time it does waitid() to reap the traced subthreads.
Unless of course systemd itself uses ptrace or forks a child with (clone_flags & CSIGNAL) != SIGCHLD. Unlikely, but who knows.
In any case I think the fix should be simple. 2/2 can help, most probably systemd can too just add __WALL to wait options.
Oleg.
Quoted 37 lines · Andrew Morton
Quoted 35 lines · On Tue, 20 Oct 2015 19:17:54 +0200 Oleg Nesterov <[email protected]> wrote:
The following program (simplified version of generated by syzkaller)
#include <pthread.h> #include <unistd.h> #include <sys/ptrace.h> #include <stdio.h> #include <signal.h> void *thread_func(void *arg) { ptrace(PTRACE_TRACEME, 0,0,0); return 0; } int main(void) { pthread_t thread; if (fork()) return 0; while (getppid() != 1) ; pthread_create(&thread, NULL, thread_func, NULL); pthread_join(thread, NULL); return 0; }creates the unreapable zombie if
/sbin/initdoesn't use__WALL.This is not a kernel bug, at least in a sense that everything works as
expected: debugger should reap a traced sub-thread before it can reap the leader, but without __WALL/__WCLONEdo_wait()ignores sub-threads.
Quoted 7 lines
Quoted 2 lines
Unfortunately, it seems that
/sbin/initin most (all?) distributions doesn't use it and we have to change the kernel to avoid the problem.Well, to fix this a distro needs to roll out a new kernel. Or a new init(8). Is there any reason to believe that distributing/deploying a new kernel is significantly easier for everyone? Because fixing init sounds like a much preferable solution to this problem.
People will continue to write new init(8) implementations, and they will miss this obscure case.
Before this bug was found, it was considered possible to use a shell script as init process. What now, every shell needs to add __WALL to its waitpids?
The use of PTRACE_TRACEME in this reproducer is clearly pathological:PTRACE_TRACEME was never intended to be used to attach to unsuspecting processes.
How about making PTRACE_TRACEME fail in this case?
Quoted 53 lines · Denys Vlasenko
Quoted 37 lines · Andrew Morton
Quoted 35 lines · On Tue, 20 Oct 2015 19:17:54 +0200 Oleg Nesterov <[email protected]> wrote:
The following program (simplified version of generated by syzkaller)
#include <pthread.h> #include <unistd.h> #include <sys/ptrace.h> #include <stdio.h> #include <signal.h> void *thread_func(void *arg) { ptrace(PTRACE_TRACEME, 0,0,0); return 0; } int main(void) { pthread_t thread; if (fork()) return 0; while (getppid() != 1) ; pthread_create(&thread, NULL, thread_func, NULL); pthread_join(thread, NULL); return 0; }creates the unreapable zombie if
/sbin/initdoesn't use__WALL.This is not a kernel bug, at least in a sense that everything works as
expected: debugger should reap a traced sub-thread before it can reap the leader, but without __WALL/__WCLONEdo_wait()ignores sub-threads.Quoted 7 lines
Quoted 2 lines
Unfortunately, it seems that
/sbin/initin most (all?) distributions doesn't use it and we have to change the kernel to avoid the problem.Well, to fix this a distro needs to roll out a new kernel. Or a new init(8). Is there any reason to believe that distributing/deploying a new kernel is significantly easier for everyone? Because fixing init sounds like a much preferable solution to this problem.
People will continue to write new init(8) implementations, and they will miss this obscure case.
Before this bug was found, it was considered possible to use a shell script as init process. What now, every shell needs to add
__WALLto its waitpids?
Quoted 5 lines
The use of
PTRACE_TRACEMEin this reproducer is clearly pathological:PTRACE_TRACEMEwas never intended to be used to attach to unsuspecting processes.How about making
PTRACE_TRACEMEfail in this case?
something like this
kernel/ptrace.c +12 -0
@@ -385,6 +385,17 @@ static int ptrace_traceme(void)write_lock_irq (&tasklist_lock);/* Are we already being traced? */ if (!current->ptrace) { +struct pid_namespace *pid_ns; + + pid_ns =task_active_pid_ns (current->parent); +if (current->parent == pid_ns->child_reaper) { +/* +* Our parent is init. We may be a reparented process +* used for PTRACE_TRACEME zombie attack on init. +*/ + goto nope; + } + ret =security_ptrace_traceme (current->parent);/* * Check PF_EXITING to ensure ->real_parent has not passed
@@ -395,6 +406,7 @@ static int ptrace_traceme(void) current->ptrace =PT_PTRACED ;__ptrace_link (current, current->real_parent); } + nope: ; }write_unlock_irq (&tasklist_lock);
Oleg Nesterov
maintainerQuoted 8 lines · Denys Vlasenko
Quoted 6 lines · Denys Vlasenko
Quoted 4 lines · Andrew Morton
Well, to fix this a distro needs to roll out a new kernel. Or a new init(8). Is there any reason to believe that distributing/deploying a new kernel is significantly easier for everyone? Because fixing init sounds like a much preferable solution to this problem.
Quoted 6 lines
Quoted 6 lines
People will continue to write new init(8) implementations, and they will miss this obscure case.
Before this bug was found, it was considered possible to use a shell script as init process. What now, every shell needs to add
__WALLto its waitpids?
Why not? I think it can safely use __WALL too.
Quoted 3 lines
Quoted 3 lines
The use of
PTRACE_TRACEMEin this reproducer is clearly pathological:PTRACE_TRACEMEwas never intended to be used to attach to unsuspecting processes.
Sure. But people do the things which were never intended to be used all the time. We simply can not know if this "feature" already has a creative user or not.
As for the patch,
kernel/ptrace.c static int ptrace_traceme(void) · line 391
Quoted 10 lines
kernel/ptrace.c +4
-0@@ -385,6 +385,17 @@ static int ptrace_traceme(void)write_lock_irq (&tasklist_lock);/* Are we already being traced? */ if (!current->ptrace) { +struct pid_namespace *pid_ns; + + pid_ns =task_active_pid_ns (current->parent); +if (current->parent == pid_ns->child_reaper) {
Well, at least this needs same_thread_group(parent, child_reaper).
Plus we have PR_SET_CHILD_SUBREAPER so we also need to traverse the ->real_parent list if has_subreaper.
Finally it is not clear which ->child_reaper we should use after setns(pidns_fd).
This all is fixable (although this again reminds me about a bug with CHILD_SUBREAPER we probably need to fix first). But I didn't even try to consider this option because it can break something.
And honestly, personally I don't like it. If we believe that we can do this because "PTRACE_TRACEME was never intended to be used to attach to unsuspecting processes", then we need a more generic change, imo.
See http://marc.info/?l=linux-kernel&m=144536282305282 . Just in case, it is not that I think "parent_exec_id != self_exec_id" is all we need. This needs more discussion.
Oleg.
Quoted 9 lines · Oleg Nesterov
Quoted 8 lines · Denys Vlasenko
Quoted 6 lines · Denys Vlasenko
Quoted 4 lines · Andrew Morton
Well, to fix this a distro needs to roll out a new kernel. Or a new init(8). Is there any reason to believe that distributing/deploying a new kernel is significantly easier for everyone? Because fixing init sounds like a much preferable solution to this problem.
Quoted 8 lines
Quoted 6 lines
Quoted 6 lines
People will continue to write new init(8) implementations, and they will miss this obscure case.
Before this bug was found, it was considered possible to use a shell script as init process. What now, every shell needs to add
__WALLto its waitpids?Why not? I think it can safely use
__WALLtoo.
Because having any userspace program which can happen to be init, which includes all shells out there in the wild
(bash, dash, ash, ksh, zsh, msh, hush,...)
learn about __WALL is wrong: apart from this wart, they do not have to use any Linux-specific code. It can all be perfectly legitimate POSIX.
Quoted 7 lines
Quoted 3 lines
Quoted 3 lines
The use of
PTRACE_TRACEMEin this reproducer is clearly pathological:PTRACE_TRACEMEwas never intended to be used to attach to unsuspecting processes.Sure. But people do the things which were never intended to be used all the time. We simply can not know if this "feature" already has a creative user or not.
It won't be unfixable: they will just have to switch from PTRACE_TRACEME to PTRACE_ATTACH.
As of now we do not know any people craz^W creative enough to create a cross between init and strace. If such specimens would materialize, don't they deserve to have to make that change?
Oleg Nesterov
maintainerQuoted 18 lines · Denys Vlasenko
Quoted 16 lines · Oleg Nesterov
Quoted 14 lines · Denys Vlasenko
Quoted 12 lines · Denys Vlasenko
Quoted 4 lines · Andrew Morton
Well, to fix this a distro needs to roll out a new kernel. Or a new init(8). Is there any reason to believe that distributing/deploying a new kernel is significantly easier for everyone? Because fixing init sounds like a much preferable solution to this problem.
People will continue to write new init(8) implementations, and they will miss this obscure case.
Before this bug was found, it was considered possible to use a shell script as init process. What now, every shell needs to add
__WALLto its waitpids?
Quoted 7 lines
Quoted 1 line
Why not? I think it can safely use
__WALLtoo.Because having any userspace program which can happen to be init, which includes all shells out there in the wild
(bash, dash, ash, ksh, zsh, msh, hush,...)
learn about__WALLis wrong: apart from this wart, they do not have to use any Linux-specific code. It can all be perfectly legitimate POSIX.
Yes, this is true. I meant that they could safely use __WALL to, but I understand that this change can be painful.
Quoted 10 lines
Quoted 3 lines
Sure. But people do the things which were never intended to be used all the time. We simply can not know if this "feature" already has a creative user or not.
It won't be unfixable: they will just have to switch from
PTRACE_TRACEMEtoPTRACE_ATTACH.As of now we do not know any people craz^W creative enough to create a cross between init and strace. If such specimens would materialize, don't they deserve to have to make that change?
This also applies to people who use bash/whatever as /sbin/init and allow the untrusted users to run the exploits ;) I do not know who is more crazy.
In any case, the real question is whether we should change the kernel to fix the problem, or ask the distros to fix their init's. In the former case 1/2 looks simpler/safer to me than the change in ptrace_traceme(), and you seem to agree that 1/2 is not that bad.
Oleg.
Quoted 41 lines · Oleg Nesterov
Quoted 18 lines · Denys Vlasenko
Quoted 16 lines · Oleg Nesterov
Quoted 14 lines · Denys Vlasenko
Quoted 12 lines · Denys Vlasenko
Quoted 4 lines · Andrew Morton
Well, to fix this a distro needs to roll out a new kernel. Or a new init(8). Is there any reason to believe that distributing/deploying a new kernel is significantly easier for everyone? Because fixing init sounds like a much preferable solution to this problem.
People will continue to write new init(8) implementations, and they will miss this obscure case.
Before this bug was found, it was considered possible to use a shell script as init process. What now, every shell needs to add
__WALLto its waitpids?Quoted 7 lines
Quoted 1 line
Why not? I think it can safely use
__WALLtoo.Because having any userspace program which can happen to be init, which includes all shells out there in the wild
(bash, dash, ash, ksh, zsh, msh, hush,...)
learn about__WALLis wrong: apart from this wart, they do not have to use any Linux-specific code. It can all be perfectly legitimate POSIX.Yes, this is true. I meant that they could safely use
__WALLto, but I understand that this change can be painful.Quoted 10 lines
Quoted 3 lines
Sure. But people do the things which were never intended to be used all the time. We simply can not know if this "feature" already has a creative user or not.
It won't be unfixable: they will just have to switch from
PTRACE_TRACEMEtoPTRACE_ATTACH.As of now we do not know any people craz^W creative enough to create a cross between init and strace. If such specimens would materialize, don't they deserve to have to make that change?
Quoted 7 lines
This also applies to people who use bash/whatever as
/sbin/initand allow the untrusted users to run the exploits ;) I do not know who is more crazy.In any case, the real question is whether we should change the kernel to fix the problem, or ask the distros to fix their init's. In the former case 1/2 looks simpler/safer to me than the change in
ptrace_traceme(), and you seem to agree that 1/2 is not that bad.
A risk here seems to be that waitpid will start returning unexpected (thread) PIDs to parent processes, and it's not unreasonable to assume that e.g., a program asserts that waitpid either returns error or a known (process) PID.
That's not an init-only issue, but something that might affect any process that runs a child that happens to decide to call PTRACE_TRACEME.
The ptrace man page says:
"A process can initiate a trace by calling fork(2) and having the resulting child do a PTRACE_TRACEME, followed (typically) by an execve(2)."
Given that, can we instead make the kernel error out on PTRACE_TRACEME issued from a non-leader thread? Then between PTRACE_TRACEME and the parent's waitpid, __WALL or !__WALL should make no difference.
(Also, in the original test case, if the child gets/raises a signal or execs before exiting, the bash/init/whatever process won't be issuing PTRACE_CONT, and the child will thus end up stuck (though should be SIGKILLable, I believe). All this because PTRACE_TRACEME is broken by design by making it be the child's choice whether to be traced...)
Thanks,
Pedro Alves
Oleg Nesterov
maintainerQuoted 10 lines · Pedro Alves
Quoted 5 lines · Oleg Nesterov
In any case, the real question is whether we should change the kernel to fix the problem, or ask the distros to fix their init's. In the former case 1/2 looks simpler/safer to me than the change in
ptrace_traceme(), and you seem to agree that 1/2 is not that bad.A risk here seems to be that waitpid will start returning unexpected (thread) PIDs to parent processes,
I don't see how this change can make the things worse,
Quoted 3 lines
and it's not unreasonable to assume
that e.g., a program asserts that waitpid either returns error or a known (process) PID.
Well. /sbin/init can never assume this, obviously.
Quoted 1 line
That's not an init-only issue,
Yes. Because we have CLONE_PARENT. So "waitpid either returns error or a known (process) PID" is only true if you trust your children.
Quoted 4 lines
but something that might affect any
process that runs a child that happens to decide to callPTRACE_TRACEME.
Quoted 8 lines
The ptrace man page says:
"A process can initiate a trace by calling fork(2) and having the resulting child do a
PTRACE_TRACEME, followed (typically) by an execve(2)."Given that, can we instead make the kernel error out on
PTRACE_TRACEMEissued from a non-leader thread? Then betweenPTRACE_TRACEMEand the parent's waitpid,__WALLor !__WALL should make no difference.
Afaics not really. A group leader can do PTRACE_TRACEME and then clone(CLONE_THREAD | CLONE_PTRACE) with the same effect.
Quoted 3 lines
(Also, in the original test case, if the child gets/raises a signal or execs before exiting, the bash/init/whatever process won't be issuing
PTRACE_CONT, and the child will thus end up stuck (though should be SIGKILLable,
Oh, but if it is killable everything is fine. How does this differ from the case when, say, you jusr reparent to init and do kill(getpid(), SIGSTOP) ?
Quoted 1 line
All this because
PTRACE_TRACEMEis broken by design
Heh. I agree. But we can't fix it now.
Oleg.
Quoted 12 lines · Oleg Nesterov
Quoted 10 lines · Pedro Alves
Quoted 5 lines · Oleg Nesterov
In any case, the real question is whether we should change the kernel to fix the problem, or ask the distros to fix their init's. In the former case 1/2 looks simpler/safer to me than the change in
ptrace_traceme(), and you seem to agree that 1/2 is not that bad.A risk here seems to be that waitpid will start returning unexpected (thread) PIDs to parent processes,
Quoted 7 lines
I don't see how this change can make the things worse,
Quoted 3 lines
and it's not unreasonable to assume
that e.g., a program asserts that waitpid either returns error or a known (process) PID.Well.
/sbin/initcan never assume this, obviously.
Right. I was actually thinking of !init processes -- basically code that spawns helper processes, keeps a data structure indexed by pid, then discards the structure when the child exits. Something like:
pid = waitpid(-1, &status, 0);
if (pid > 0)
{
struct child_process *child = find_process(pid); assert (child != NULL);
}
As in, before your change, the child could get stuck forever, but after your change, the parent could die/assert instead.
But ...
Quoted 5 lines
Quoted 2 lines
That's not an init-only issue,
Yes. Because we have
CLONE_PARENT. So "waitpid either returns error or a known (process) PID" is only true if you trust your children.
... OK, that's indeed a good point.
Quoted 6 lines
Quoted 3 lines
(Also, in the original test case, if the child gets/raises a signal or execs before exiting, the bash/init/whatever process won't be issuing
PTRACE_CONT, and the child will thus end up stuck (though should be SIGKILLable,Oh, but if it is killable everything is fine. How does this differ from the case when, say, you jusr reparent to init and do
kill(getpid(), SIGSTOP)?
The difference is that if the child called PTRACE_TRACEME, then it goes to ptrace-stop instead and no amount of SIGCONT unstucks it -- the only way out is force killing. I agree it's not a major issue as there's a way out (and thus made it a parens), but I wouldn't call it nice either.
Quoted 3 lines
Quoted 1 line
All this because
PTRACE_TRACEMEis broken by designHeh. I agree. But we can't fix it now.
Perhaps the man page could document it as deprecated, suggesting PTRACE_ATTACH/PTRACE_SEIZE instead?
Thanks,
Pedro Alves
Oleg Nesterov
maintainerQuoted 8 lines · Pedro Alves
Quoted 6 lines · Oleg Nesterov
Quoted 4 lines · Pedro Alves
(Also, in the original test case, if the child gets/raises a signal or execs before exiting, the bash/init/whatever process won't be issuing
PTRACE_CONT, and the child will thus end up stuck (though should be SIGKILLable,
Quoted 7 lines
Quoted 2 lines
Oh, but if it is killable everything is fine. How does this differ from the case when, say, you jusr reparent to init and do
kill(getpid(), SIGSTOP)?The difference is that if the child called
PTRACE_TRACEME, then it goes to ptrace-stop instead and no amount of SIGCONT unstucks it -- the only way out is force killing. I agree it's not a major issue as there's a way out (and thus made it a parens), but I wouldn't call it nice either.
IOW, the difference is that it is TASK_TRACED, not TASK_STOPPED. I agree, this is not nice. But this is not nice simply because PTRACE_TRACEME is not nice.
Quoted 6 lines
Quoted 3 lines
Quoted 1 line
All this because
PTRACE_TRACEMEis broken by designHeh. I agree. But we can't fix it now.
Perhaps the man page could document it as deprecated, suggesting PTRACE_ATTACH/PTRACE_SEIZE instead?
I don't know. but I won't mind if you mark PTRACE_ATTACH as deprecated too ;) PTRACE_SEIZE can be used instead and it doesn't abuse SIGSTOP.
Oleg.
Quoted 13 lines · Oleg Nesterov
creates the unreapable zombie if
/sbin/initdoesn't use__WALL.This is not a kernel bug, at least in a sense that everything works as
expected: debugger should reap a traced sub-thread before it can reap the leader, but without __WALL/__WCLONEdo_wait()ignores sub-threads.Unfortunately, it seems that
/sbin/initin most (all?) distributions doesn't use it and we have to change the kernel to avoid the problem.This patch just adds the "ptrace" check into
eligible_child(). To some degree this matches the "tsk->ptrace" inexit_notify(), ->exit_signal is mostly ignored when the tracee reports to debugger.
Quoted 7 lines
This obviously means the user-visible change:
__WCLONEand__WALLno longer have any meaning for debugger. And I can only hope that this won't break something.We could make a more conservative change. Say, we can take
__WCLONEinto account, or !thread_group_leader(). But it would be nice to not complicate these historical/confusing checks.
For the record, I like this way of fixing the problem too.
It removes the need to ever use __WALL in userspace:
IIRC the only case where it was needed are debuggers.
Which in turn makes *all* underscored wait flags (__WCLONE, __WALL, __WNOTHREAD) unnecessary for any sane application use.
Oleg Nesterov
maintainerPATCH 2/2 wait: allow sys_waitid() to use __WNOTHREAD/__WCLONE/__WALL
In reply to Oleg NesterovI see no reason why waitid() can't support other linux-specific flags allowed in sys_wait4().
--- kernel/exit.c | 3 ++- 1 files changed, 2 insertions(+), 1 deletions(-)
kernel/exit.c +2 -1
@@ -1532,7 +1532,8 @@ SYSCALL_DEFINE5(waitid, int, which, pid_t, upid, struct siginfo __user *,enum pid_type type;long ret; -if (options & ~(WNOHANG |WNOWAIT |WEXITED |WSTOPPED |WCONTINUED )) +if (options & ~(WNOHANG |WNOWAIT |WEXITED |WSTOPPED |WCONTINUED | + __WNOTHREAD|__WCLONE|__WALL))return -EINVAL ;if (!(options & (WEXITED |WSTOPPED |WCONTINUED )))return -EINVAL ;
Signature · 1 line
1.5.5.1
Oleg Nesterov
maintainerForgot to say...
Another question is why PTRACE_TRACEME succeeds in this case. I guess it is to late to change (break) the rules, but I never understood the security checks. The comment above cap_ptrace_traceme() says:
Determine whether another process may trace the current
and "another process" is parent. To me this looks strange, imo we should determine whether the current may abuse its parent. So perhaps we could change ptrace_traceme() to fail if
current->parent_exec_id != parent->self_exec_id
?
But this too can break something. Although I can't imagine why the child reaper or a PR_SET_CHILD_SUBREAPER process may want to trace the reparented tasks.
Quoted 27 lines · Oleg Nesterov
Damn. I simply do not know what should/can we do. From the change log:
And I can only hope that this won't break something.
yet this patch cc's -stable.
Please see the changelog, but in short: this is not a kernel bug but unlikely we can fix all distributions, so I think we have to change the kernel.
HOWEVER. With this change
__WCLONEand__WALLhave no effect for debugger,do_wait()works as if__WALLis set if the child (natural or not) is traced.Jan, Pedro, could you please confirm this won't break gdb? I tried to look into gdb-7.1, and at first glance gdb uses
__WCLONEonly because__WALLdoesn't work on older kernels, iow it seems to me that gdb actually wants__WALLso this change should be fine.Any other ideas?
Oleg.
Quoted 4 lines · Oleg Nesterov
Jan, Pedro, could you please confirm this won't break gdb? I tried to look into gdb-7.1, and at first glance gdb uses
__WCLONEonly because__WALLdoesn't work on older kernels, iow it seems to me that gdb actually wants__WALLso this change should be fine.
Right, gdb actually wants __WALL, but it doesn't use it to keep compatibility with kernels that predate it.
gdb nowadays has an __WALL emulation waitpid wrapper (alternates __WCLONE+WNOHANG with 0+WNOHANG, blocks on sigsuspend/SIGCHLD):
Though it's not used everywhere. Some older code in the ptrace backend open codes the "try __WCLONE, then try !__WCLONE." dance.
Seems like __WALL was added in Linux 2.4; gdb could probably assume it's available nowadays...
In any case, to make sure existing gdb binaries would still work with your kernel change, I ran GDB's testsuite with this:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gdb/nat/linux-waitpid.c +14 -0
@@ -149,3 +149,17 @@ my_waitpid (int pid, int *status, int flags) errno = out_errno;return ret; } + +#include <dlfcn.h> + +pid_t +waitpid (pid_t pid,int *status,int options) +{ +static pid_t (*waitpid2) (pid_t pid,int *status,int options) =NULL ; + +if (waitpid2 ==NULL ) + waitpid2 =dlsym (RTLD_NEXT ,"waitpid" ); + + options |= __WALL; +return waitpid2 (pid, status, options); +}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
and got no regressions. So seems like all would be well from GDB's perspective.
Thanks,
Pedro Alves
Oleg Nesterov
maintainerQuoted 27 lines · Pedro Alves
In any case, to make sure existing gdb binaries would still work with your kernel change, I ran GDB's testsuite with this:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
gdb/nat/linux-waitpid.c +14
-0@@ -149,3 +149,17 @@ my_waitpid (int pid, int *status, int flags) errno = out_errno;return ret; } + +#include <dlfcn.h> + +pid_t +waitpid (pid_t pid,int *status,int options) +{ +static pid_t (*waitpid2) (pid_t pid,int *status,int options) =NULL ; + +if (waitpid2 ==NULL ) + waitpid2 =dlsym (RTLD_NEXT ,"waitpid" ); + + options |= __WALL; +return waitpid2 (pid, status, options); +}
Quoted 1 line
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Thanks a lot Pedro!
So gdb should be fine, strace too. Perhaps we should change the kernel this way and forget about /sbin/init fixes.
Oleg.