]>
Commit | Line | Data |
---|---|---|
f8aa8e94 EB |
1 | /* |
2 | * nsenter(1) - command-line interface for setns(2) | |
3 | * | |
4 | * Copyright (C) 2012-2013 Eric Biederman <ebiederm@xmission.com> | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License as published by the | |
8 | * Free Software Foundation; version 2. | |
9 | * | |
10 | * This program is distributed in the hope that it will be useful, but | |
11 | * WITHOUT ANY WARRANTY; without even the implied warranty of | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
13 | * General Public License for more details. | |
14 | * | |
15 | * You should have received a copy of the GNU General Public License along | |
16 | * with this program; if not, write to the Free Software Foundation, Inc., | |
17 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
18 | */ | |
19 | ||
f8aa8e94 EB |
20 | #include <dirent.h> |
21 | #include <errno.h> | |
22 | #include <getopt.h> | |
23 | #include <sched.h> | |
24 | #include <stdio.h> | |
25 | #include <stdlib.h> | |
984e1b7c | 26 | #include <stdbool.h> |
f8aa8e94 | 27 | #include <unistd.h> |
dfd8b117 | 28 | #include <assert.h> |
57580694 ZJS |
29 | #include <sys/types.h> |
30 | #include <sys/wait.h> | |
f8aa8e94 | 31 | |
0d3ec860 | 32 | #include "strutils.h" |
f8aa8e94 EB |
33 | #include "nls.h" |
34 | #include "c.h" | |
35 | #include "closestream.h" | |
c91280a4 | 36 | #include "namespace.h" |
57580694 | 37 | #include "exec_shell.h" |
f8aa8e94 | 38 | |
a167328a | 39 | static struct namespace_file { |
f8aa8e94 | 40 | int nstype; |
f9bbdea6 | 41 | const char *name; |
f8aa8e94 EB |
42 | int fd; |
43 | } namespace_files[] = { | |
ebbc87cd | 44 | /* Careful the order is significant in this array. |
f8aa8e94 EB |
45 | * |
46 | * The user namespace comes first, so that it is entered | |
47 | * first. This gives an unprivileged user the potential to | |
48 | * enter the other namespaces. | |
49 | */ | |
50 | { .nstype = CLONE_NEWUSER, .name = "ns/user", .fd = -1 }, | |
51 | { .nstype = CLONE_NEWIPC, .name = "ns/ipc", .fd = -1 }, | |
52 | { .nstype = CLONE_NEWUTS, .name = "ns/uts", .fd = -1 }, | |
53 | { .nstype = CLONE_NEWNET, .name = "ns/net", .fd = -1 }, | |
54 | { .nstype = CLONE_NEWPID, .name = "ns/pid", .fd = -1 }, | |
55 | { .nstype = CLONE_NEWNS, .name = "ns/mnt", .fd = -1 }, | |
9905912f | 56 | { .nstype = 0, .name = NULL, .fd = -1 } |
f8aa8e94 EB |
57 | }; |
58 | ||
59 | static void usage(int status) | |
60 | { | |
61 | FILE *out = status == EXIT_SUCCESS ? stdout : stderr; | |
62 | ||
63 | fputs(USAGE_HEADER, out); | |
64 | fprintf(out, _(" %s [options] <program> [args...]\n"), | |
65 | program_invocation_short_name); | |
66 | ||
67 | fputs(USAGE_OPTIONS, out); | |
26f879ed SK |
68 | fputs(_(" -t, --target <pid> target process to get namespaces from\n"), out); |
69 | fputs(_(" -m, --mount [=<file>] enter mount namespace\n"), out); | |
70 | fputs(_(" -u, --uts [=<file>] enter UTS namespace (hostname etc)\n"), out); | |
71 | fputs(_(" -i, --ipc [=<file>] enter System V IPC namespace\n"), out); | |
72 | fputs(_(" -n, --net [=<file>] enter network namespace\n"), out); | |
73 | fputs(_(" -p, --pid [=<file>] enter pid namespace\n"), out); | |
74 | fputs(_(" -U, --user [=<file>] enter user namespace\n"), out); | |
6b9e5bf6 RW |
75 | fputs(_(" -S, --setuid <uid> set uid in user namespace\n"), out); |
76 | fputs(_(" -G, --setgid <gid> set gid in user namespace\n"), out); | |
26f879ed SK |
77 | fputs(_(" -r, --root [=<dir>] set the root directory\n"), out); |
78 | fputs(_(" -w, --wd [=<dir>] set the working directory\n"), out); | |
79 | fputs(_(" -F, --no-fork do not fork before exec'ing <program>\n"), out); | |
80 | ||
f8aa8e94 EB |
81 | fputs(USAGE_SEPARATOR, out); |
82 | fputs(USAGE_HELP, out); | |
83 | fputs(USAGE_VERSION, out); | |
84 | fprintf(out, USAGE_MAN_TAIL("nsenter(1)")); | |
85 | ||
86 | exit(status); | |
87 | } | |
88 | ||
89 | static pid_t namespace_target_pid = 0; | |
90 | static int root_fd = -1; | |
91 | static int wd_fd = -1; | |
92 | ||
f9bbdea6 | 93 | static void open_target_fd(int *fd, const char *type, const char *path) |
f8aa8e94 EB |
94 | { |
95 | char pathbuf[PATH_MAX]; | |
96 | ||
97 | if (!path && namespace_target_pid) { | |
98 | snprintf(pathbuf, sizeof(pathbuf), "/proc/%u/%s", | |
a167328a | 99 | namespace_target_pid, type); |
f8aa8e94 EB |
100 | path = pathbuf; |
101 | } | |
102 | if (!path) | |
a167328a SK |
103 | errx(EXIT_FAILURE, |
104 | _("neither filename nor target pid supplied for %s"), | |
105 | type); | |
f8aa8e94 EB |
106 | |
107 | if (*fd >= 0) | |
108 | close(*fd); | |
109 | ||
110 | *fd = open(path, O_RDONLY); | |
111 | if (*fd < 0) | |
8b7a7750 | 112 | err(EXIT_FAILURE, _("cannot open %s"), path); |
f8aa8e94 EB |
113 | } |
114 | ||
f9bbdea6 | 115 | static void open_namespace_fd(int nstype, const char *path) |
f8aa8e94 EB |
116 | { |
117 | struct namespace_file *nsfile; | |
118 | ||
119 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) { | |
120 | if (nstype != nsfile->nstype) | |
121 | continue; | |
122 | ||
123 | open_target_fd(&nsfile->fd, nsfile->name, path); | |
124 | return; | |
125 | } | |
126 | /* This should never happen */ | |
dfd8b117 | 127 | assert(nsfile->nstype); |
f8aa8e94 EB |
128 | } |
129 | ||
c9515f86 EB |
130 | static void continue_as_child(void) |
131 | { | |
132 | pid_t child = fork(); | |
133 | int status; | |
134 | pid_t ret; | |
135 | ||
136 | if (child < 0) | |
137 | err(EXIT_FAILURE, _("fork failed")); | |
138 | ||
139 | /* Only the child returns */ | |
140 | if (child == 0) | |
141 | return; | |
142 | ||
143 | for (;;) { | |
144 | ret = waitpid(child, &status, WUNTRACED); | |
145 | if ((ret == child) && (WIFSTOPPED(status))) { | |
146 | /* The child suspended so suspend us as well */ | |
147 | kill(getpid(), SIGSTOP); | |
148 | kill(child, SIGCONT); | |
149 | } else { | |
150 | break; | |
151 | } | |
152 | } | |
153 | /* Return the child's exit code if possible */ | |
154 | if (WIFEXITED(status)) { | |
155 | exit(WEXITSTATUS(status)); | |
a167328a | 156 | } else if (WIFSIGNALED(status)) { |
c9515f86 EB |
157 | kill(getpid(), WTERMSIG(status)); |
158 | } | |
159 | exit(EXIT_FAILURE); | |
160 | } | |
161 | ||
f8aa8e94 EB |
162 | int main(int argc, char *argv[]) |
163 | { | |
164 | static const struct option longopts[] = { | |
165 | { "help", no_argument, NULL, 'h' }, | |
166 | { "version", no_argument, NULL, 'V'}, | |
167 | { "target", required_argument, NULL, 't' }, | |
168 | { "mount", optional_argument, NULL, 'm' }, | |
169 | { "uts", optional_argument, NULL, 'u' }, | |
170 | { "ipc", optional_argument, NULL, 'i' }, | |
171 | { "net", optional_argument, NULL, 'n' }, | |
172 | { "pid", optional_argument, NULL, 'p' }, | |
173 | { "user", optional_argument, NULL, 'U' }, | |
6b9e5bf6 RW |
174 | { "setuid", required_argument, NULL, 'S' }, |
175 | { "setgid", required_argument, NULL, 'G' }, | |
f8aa8e94 EB |
176 | { "root", optional_argument, NULL, 'r' }, |
177 | { "wd", optional_argument, NULL, 'w' }, | |
28384adc | 178 | { "no-fork", no_argument, NULL, 'F' }, |
f8aa8e94 EB |
179 | { NULL, 0, NULL, 0 } |
180 | }; | |
181 | ||
182 | struct namespace_file *nsfile; | |
984e1b7c | 183 | int c, namespaces = 0; |
57dbcf94 ZJS |
184 | bool do_rd = false, do_wd = false; |
185 | int do_fork = -1; /* unknown yet */ | |
6b9e5bf6 RW |
186 | uid_t uid = 0; |
187 | gid_t gid = 0; | |
f8aa8e94 | 188 | |
999ac5e2 | 189 | setlocale(LC_ALL, ""); |
f8aa8e94 EB |
190 | bindtextdomain(PACKAGE, LOCALEDIR); |
191 | textdomain(PACKAGE); | |
192 | atexit(close_stdout); | |
193 | ||
28384adc | 194 | while ((c = |
6b9e5bf6 | 195 | getopt_long(argc, argv, "hVt:m::u::i::n::p::U::S:G:r::w::F", |
28384adc ZJS |
196 | longopts, NULL)) != -1) { |
197 | switch (c) { | |
f8aa8e94 EB |
198 | case 'h': |
199 | usage(EXIT_SUCCESS); | |
200 | case 'V': | |
201 | printf(UTIL_LINUX_VERSION); | |
202 | return EXIT_SUCCESS; | |
203 | case 't': | |
a167328a SK |
204 | namespace_target_pid = |
205 | strtoul_or_err(optarg, _("failed to parse pid")); | |
f8aa8e94 EB |
206 | break; |
207 | case 'm': | |
984e1b7c ZJS |
208 | if (optarg) |
209 | open_namespace_fd(CLONE_NEWNS, optarg); | |
210 | else | |
211 | namespaces |= CLONE_NEWNS; | |
f8aa8e94 EB |
212 | break; |
213 | case 'u': | |
984e1b7c ZJS |
214 | if (optarg) |
215 | open_namespace_fd(CLONE_NEWUTS, optarg); | |
216 | else | |
217 | namespaces |= CLONE_NEWUTS; | |
f8aa8e94 EB |
218 | break; |
219 | case 'i': | |
984e1b7c ZJS |
220 | if (optarg) |
221 | open_namespace_fd(CLONE_NEWIPC, optarg); | |
222 | else | |
223 | namespaces |= CLONE_NEWIPC; | |
f8aa8e94 EB |
224 | break; |
225 | case 'n': | |
984e1b7c ZJS |
226 | if (optarg) |
227 | open_namespace_fd(CLONE_NEWNET, optarg); | |
228 | else | |
229 | namespaces |= CLONE_NEWNET; | |
f8aa8e94 EB |
230 | break; |
231 | case 'p': | |
984e1b7c ZJS |
232 | if (optarg) |
233 | open_namespace_fd(CLONE_NEWPID, optarg); | |
234 | else | |
235 | namespaces |= CLONE_NEWPID; | |
f8aa8e94 EB |
236 | break; |
237 | case 'U': | |
984e1b7c ZJS |
238 | if (optarg) |
239 | open_namespace_fd(CLONE_NEWUSER, optarg); | |
240 | else | |
241 | namespaces |= CLONE_NEWUSER; | |
f8aa8e94 | 242 | break; |
6b9e5bf6 RW |
243 | case 'S': |
244 | uid = strtoul_or_err(optarg, _("failed to parse uid")); | |
245 | break; | |
246 | case 'G': | |
247 | gid = strtoul_or_err(optarg, _("failed to parse gid")); | |
248 | break; | |
28384adc | 249 | case 'F': |
57dbcf94 | 250 | do_fork = 0; |
f8aa8e94 EB |
251 | break; |
252 | case 'r': | |
984e1b7c ZJS |
253 | if (optarg) |
254 | open_target_fd(&root_fd, "root", optarg); | |
255 | else | |
256 | do_rd = true; | |
f8aa8e94 EB |
257 | break; |
258 | case 'w': | |
984e1b7c ZJS |
259 | if (optarg) |
260 | open_target_fd(&wd_fd, "cwd", optarg); | |
261 | else | |
262 | do_wd = true; | |
f8aa8e94 EB |
263 | break; |
264 | default: | |
265 | usage(EXIT_FAILURE); | |
266 | } | |
267 | } | |
268 | ||
984e1b7c ZJS |
269 | /* |
270 | * Open remaining namespace and directory descriptors. | |
271 | */ | |
272 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) | |
273 | if (nsfile->nstype & namespaces) | |
274 | open_namespace_fd(nsfile->nstype, NULL); | |
275 | if (do_rd) | |
276 | open_target_fd(&root_fd, "root", NULL); | |
277 | if (do_wd) | |
278 | open_target_fd(&wd_fd, "cwd", NULL); | |
279 | ||
f8aa8e94 EB |
280 | /* |
281 | * Now that we know which namespaces we want to enter, enter them. | |
282 | */ | |
283 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) { | |
284 | if (nsfile->fd < 0) | |
285 | continue; | |
57dbcf94 ZJS |
286 | if (nsfile->nstype == CLONE_NEWPID && do_fork == -1) |
287 | do_fork = 1; | |
f8aa8e94 | 288 | if (setns(nsfile->fd, nsfile->nstype)) |
a167328a SK |
289 | err(EXIT_FAILURE, |
290 | _("reassociate to namespace '%s' failed"), | |
f8aa8e94 EB |
291 | nsfile->name); |
292 | close(nsfile->fd); | |
293 | nsfile->fd = -1; | |
294 | } | |
295 | ||
296 | /* Remember the current working directory if I'm not changing it */ | |
297 | if (root_fd >= 0 && wd_fd < 0) { | |
298 | wd_fd = open(".", O_RDONLY); | |
299 | if (wd_fd < 0) | |
a167328a SK |
300 | err(EXIT_FAILURE, |
301 | _("cannot open current working directory")); | |
f8aa8e94 EB |
302 | } |
303 | ||
304 | /* Change the root directory */ | |
305 | if (root_fd >= 0) { | |
306 | if (fchdir(root_fd) < 0) | |
a167328a SK |
307 | err(EXIT_FAILURE, |
308 | _("change directory by root file descriptor failed")); | |
f8aa8e94 EB |
309 | |
310 | if (chroot(".") < 0) | |
311 | err(EXIT_FAILURE, _("chroot failed")); | |
312 | ||
313 | close(root_fd); | |
314 | root_fd = -1; | |
315 | } | |
316 | ||
317 | /* Change the working directory */ | |
318 | if (wd_fd >= 0) { | |
319 | if (fchdir(wd_fd) < 0) | |
a167328a SK |
320 | err(EXIT_FAILURE, |
321 | _("change directory by working directory file descriptor failed")); | |
f8aa8e94 EB |
322 | |
323 | close(wd_fd); | |
324 | wd_fd = -1; | |
325 | } | |
326 | ||
57dbcf94 | 327 | if (do_fork == 1) |
c9515f86 | 328 | continue_as_child(); |
f8aa8e94 | 329 | |
6b9e5bf6 RW |
330 | if (namespaces & CLONE_NEWUSER) { |
331 | if (setuid(uid) < 0) | |
332 | err(EXIT_FAILURE, _("setuid failed")); | |
333 | if (setgid(gid) < 0) | |
334 | err(EXIT_FAILURE, _("setgid failed")); | |
335 | } | |
336 | ||
57580694 ZJS |
337 | if (optind < argc) { |
338 | execvp(argv[optind], argv + optind); | |
339 | err(EXIT_FAILURE, _("failed to execute %s"), argv[optind]); | |
340 | } | |
341 | exec_shell(); | |
f8aa8e94 | 342 | } |