Gentoo Archives: gentoo-portage-dev

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

Replies