RFC: Enforcing process hierarchies (`prctl` related)
Ciprian Dorin Craciun
ciprian.craciun at gmail.com
Fri Aug 24 12:20:57 EDT 2012
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.
More information about the Kernelnewbies
mailing list