1 |
commit: fb406579b1d13c1ba23b28e0bb794c22878a58c0 |
2 |
Author: Zac Medico <zmedico <AT> gentoo <DOT> org> |
3 |
AuthorDate: Sun Jan 13 23:06:35 2019 +0000 |
4 |
Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> |
5 |
CommitDate: Tue Jan 15 05:45:55 2019 +0000 |
6 |
URL: https://gitweb.gentoo.org/proj/portage.git/commit/?id=fb406579 |
7 |
|
8 |
pid-sandbox: execute pid-ns-init as pid 1 (bug 675312) |
9 |
|
10 |
Execute pid-ns-init as the first fork after unshare, as |
11 |
required for it to have pid 1 and become the default reaper |
12 |
of orphaned descendant processes. In _exec, exec a separate |
13 |
pid-ns-init process to behave as a supervisor which will |
14 |
forward signals to init and forward exit status to the parent |
15 |
process. |
16 |
|
17 |
Fixes: a75d5546e3a4 ("Introduce a tiny init replacement for inside pid namespace") |
18 |
Bug: https://bugs.gentoo.org/675312 |
19 |
Reviewed-by: Brian Dolbec <dolsen <AT> gentoo.org> |
20 |
Signed-off-by: Zac Medico <zmedico <AT> gentoo.org> |
21 |
|
22 |
bin/pid-ns-init | 44 ++++++++++++++++++++++++++++++++++++++++---- |
23 |
lib/portage/process.py | 25 +++++++++++++++++++------ |
24 |
2 files changed, 59 insertions(+), 10 deletions(-) |
25 |
|
26 |
diff --git a/bin/pid-ns-init b/bin/pid-ns-init |
27 |
index 843257b70..182d00a43 100644 |
28 |
--- a/bin/pid-ns-init |
29 |
+++ b/bin/pid-ns-init |
30 |
@@ -1,23 +1,59 @@ |
31 |
#!/usr/bin/env python |
32 |
-# Copyright 2018 Gentoo Authors |
33 |
+# Copyright 2018-2019 Gentoo Authors |
34 |
# Distributed under the terms of the GNU General Public License v2 |
35 |
|
36 |
+import functools |
37 |
import os |
38 |
+import signal |
39 |
import sys |
40 |
|
41 |
|
42 |
+KILL_SIGNALS = ( |
43 |
+ signal.SIGINT, |
44 |
+ signal.SIGTERM, |
45 |
+ signal.SIGHUP, |
46 |
+) |
47 |
+ |
48 |
+def forward_kill_signal(main_child_pid, signum, frame): |
49 |
+ os.kill(main_child_pid, signum) |
50 |
+ |
51 |
+ |
52 |
def main(argv): |
53 |
if len(argv) < 2: |
54 |
- return 'Usage: {} <main-child-pid>'.format(argv[0]) |
55 |
- main_child_pid = int(argv[1]) |
56 |
+ return 'Usage: {} <main-child-pid> or <binary> <argv0> [arg]..'.format(argv[0]) |
57 |
+ |
58 |
+ if len(argv) == 2: |
59 |
+ # The child process is init (pid 1) in a child pid namespace, and |
60 |
+ # the current process supervises from within the global pid namespace |
61 |
+ # (forwarding signals to init and forwarding exit status to the parent |
62 |
+ # process). |
63 |
+ main_child_pid = int(argv[1]) |
64 |
+ else: |
65 |
+ # The current process is init (pid 1) in a child pid namespace. |
66 |
+ binary = argv[1] |
67 |
+ args = argv[2:] |
68 |
+ |
69 |
+ main_child_pid = os.fork() |
70 |
+ if main_child_pid == 0: |
71 |
+ os.execv(binary, args) |
72 |
+ |
73 |
+ sig_handler = functools.partial(forward_kill_signal, main_child_pid) |
74 |
+ for signum in KILL_SIGNALS: |
75 |
+ signal.signal(signum, sig_handler) |
76 |
|
77 |
# wait for child processes |
78 |
while True: |
79 |
- pid, status = os.wait() |
80 |
+ try: |
81 |
+ pid, status = os.wait() |
82 |
+ except OSError as e: |
83 |
+ if e.errno == errno.EINTR: |
84 |
+ continue |
85 |
+ raise |
86 |
if pid == main_child_pid: |
87 |
if os.WIFEXITED(status): |
88 |
return os.WEXITSTATUS(status) |
89 |
elif os.WIFSIGNALED(status): |
90 |
+ signal.signal(os.WTERMSIG(status), signal.SIG_DFL) |
91 |
os.kill(os.getpid(), os.WTERMSIG(status)) |
92 |
# go to the unreachable place |
93 |
break |
94 |
|
95 |
diff --git a/lib/portage/process.py b/lib/portage/process.py |
96 |
index 7103b6b31..6af3ac37d 100644 |
97 |
--- a/lib/portage/process.py |
98 |
+++ b/lib/portage/process.py |
99 |
@@ -564,15 +564,28 @@ def _exec(binary, mycommand, opt_name, fd_pipes, |
100 |
noiselevel=-1) |
101 |
else: |
102 |
if unshare_pid: |
103 |
- # pid namespace requires us to become init |
104 |
- fork_ret = os.fork() |
105 |
- if fork_ret != 0: |
106 |
- os.execv(portage._python_interpreter, [ |
107 |
+ main_child_pid = os.fork() |
108 |
+ if main_child_pid == 0: |
109 |
+ # pid namespace requires us to become init |
110 |
+ binary, myargs = portage._python_interpreter, [ |
111 |
portage._python_interpreter, |
112 |
os.path.join(portage._bin_path, |
113 |
'pid-ns-init'), |
114 |
- '%s' % fork_ret, |
115 |
- ]) |
116 |
+ binary] + myargs |
117 |
+ else: |
118 |
+ # Execute a supervisor process which will forward |
119 |
+ # signals to init and forward exit status to the |
120 |
+ # parent process. The supervisor process runs in |
121 |
+ # the global pid namespace, so skip /proc remount |
122 |
+ # and other setup that's intended only for the |
123 |
+ # init process. |
124 |
+ binary, myargs = portage._python_interpreter, [ |
125 |
+ portage._python_interpreter, |
126 |
+ os.path.join(portage._bin_path, |
127 |
+ 'pid-ns-init'), str(main_child_pid)] |
128 |
+ |
129 |
+ os.execve(binary, myargs, env) |
130 |
+ |
131 |
if unshare_mount: |
132 |
# mark the whole filesystem as slave to avoid |
133 |
# mounts escaping the namespace |