RFC: Enforcing process hierarchies (`prctl` related)
Hello all! I'm about to write quite a long-ish email, but I provide a small summary first for the "busy". :) == Quick summary The context is simple: * the developer wants to write a C application (call it a "controller") that spawns other child processes, which in their turn spawn other processes and so on; * some of these spawned processes might double fork (i.e. they daemonize); * these child processes are not written by the developer in question; (thus we can't "fix" them...) * the controller will run with no special privileges (i.e. non-root, no capabilities, etc.); * (for purely practical reasons, thus let's not start a flame war) we can assume a Linux-only environment, thus portability is not an issue; (the developer is also not scared in applying patches to the kernel...) The problem I try to find a solution to is again quite simple, how can the developer write the "controller" application such that the following happens: (A) any orphaned child of a sub-process (i.e. double forking ones, ones whose parent has exited, etc.) are reparented to the controller; (this is already solved in 3.4;) (B) the controller should be able to kill all its subprocesses (and their subprocesses, recursively); (C) if the controller dies (segfault, killed, etc.) all its subprocesses are terminated (and recursively so); Thus, what "magic" :) should one invoke to obtain the above? == Use cases To better put the problem into a context, the previous features would allow one to implement something like: * "personal" service control systems (like `systemd`, `runit`, `daemon-tools`, etc.); * constrain badly written `sh` scripts that if terminated leak processes; or build systems (like `make`), or SSH sessions, or cron jobs, etc.; * a building block in a PaaS underlaying; But certainly NOT for "security" purposes... == Existing primitives I've really tried thinking about this for the last year or so, and I've not managed to get a "good" enough solution... But I've found the following partial solutions, but all of them have issues... (1) `clone (CLONE_NEWPID)` -> solves all the problems, but has major drawbacks: * it requires special rights (root or capability); * it creates an alternative process namespace thus will confuse the hell out of scripts assuming PID equivalence... (2) `prctl (PR_SET_CHILD_SUBREAPER)` -> solves the issue (A) (reparenting), but that's it. (3) `prctl (PR_SET_PDEATHSIG)` -> solves the issue (C) (forced termination), but it has the following issues: * it works only for the first level of sub-processes, as it must be explicitly set after each `fork`; * thus it is not "inheritable" by new children; * it doesn't work for `setuid` processes (due to possible security issue); (4) `killpg (group)` -> solves the issue (B), but: * any child could choose to change its process group; * if the controller uses its own group it kills itself; Have I missed something? == Brainstorming It would have been nice if either of the following would happen (in order of preference): (a) maybe an additional `PR_SET_CHILD_SUBREAPER`-like flag that would make the controller process really behave like a sub-init, that is on its termination terminate all its children recursively; (b) `PR_SET_PDEATHSIG` would be inheritable at least for non `setuid` processes; (maybe even better if the signal is `SIGKILL` keep it even for `setuid` processes as I don't see too much harm there...) (c) have some way to send a signal to all a process children; (d) (last resort solution we have today) iterate through `/proc/$PID` and find all the controller children, SIGKILL them, and continue until no process is found, then die; but it doesn't solve (C)... Something else? Hope I find some solution to this... (If this is not the mailing list to send it to, please point me in the right direction.) Thanks in advance for feedback, Ciprian.
Hi........ On Fri, Aug 24, 2012 at 11:20 PM, Ciprian Dorin Craciun <ciprian.craciun@gmail.com> wrote:
(d) (last resort solution we have today) iterate through `/proc/$PID` and find all the controller children, SIGKILL them, and continue until no process is found, then die; but it doesn't solve (C)...
IMHO this is the way to do it....but, to tackle C, maybe a signal handler to SIGTERM could do it. But wait, I forget whether SIGTERM could be intercepted..... -- regards, Mulyadi Santosa Freelance Linux trainer and consultant blog: the-hydra.blogspot.com training: mulyaditraining.blogspot.com
On Sat, Aug 25, 2012 at 8:48 PM, Mulyadi Santosa <mulyadi.santosa@gmail.com> wrote:
Hi........
On Fri, Aug 24, 2012 at 11:20 PM, Ciprian Dorin Craciun <ciprian.craciun@gmail.com> wrote:
(d) (last resort solution we have today) iterate through `/proc/$PID` and find all the controller children, SIGKILL them, and continue until no process is found, then die; but it doesn't solve (C)...
IMHO this is the way to do it....but, to tackle C, maybe a signal handler to SIGTERM could do it. But wait, I forget whether SIGTERM could be intercepted.....
Solving (C) (that is terminating all children if the controller suddenly dies) is impossible to achieve from the controller process itself as if it dies it can't proceed to killing the children processes, thus they are leaking to the next process reaper (most probably `init` itself). (The only way to solve this is to use the `clone (CLONE_NEWPID)`, but as said you need special privileges to call it...)
participants (2)
-
Ciprian Dorin Craciun -
Mulyadi Santosa