]>
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> | |
99d7e174 | 31 | #include <grp.h> |
974cc006 | 32 | #include <sys/stat.h> |
f8aa8e94 | 33 | |
355ee3b8 KZ |
34 | #ifdef HAVE_LIBSELINUX |
35 | # include <selinux/selinux.h> | |
36 | #endif | |
37 | ||
0d3ec860 | 38 | #include "strutils.h" |
f8aa8e94 EB |
39 | #include "nls.h" |
40 | #include "c.h" | |
41 | #include "closestream.h" | |
c91280a4 | 42 | #include "namespace.h" |
57580694 | 43 | #include "exec_shell.h" |
f8aa8e94 | 44 | |
a167328a | 45 | static struct namespace_file { |
f8aa8e94 | 46 | int nstype; |
f9bbdea6 | 47 | const char *name; |
f8aa8e94 EB |
48 | int fd; |
49 | } namespace_files[] = { | |
ebbc87cd | 50 | /* Careful the order is significant in this array. |
f8aa8e94 | 51 | * |
854d0fef JB |
52 | * The user namespace comes either first or last: first if |
53 | * you're using it to increase your privilege and last if | |
54 | * you're using it to decrease. We enter the namespaces in | |
55 | * two passes starting initially from offset 1 and then offset | |
56 | * 0 if that fails. | |
f8aa8e94 | 57 | */ |
f9e7b66d SH |
58 | { .nstype = CLONE_NEWUSER, .name = "ns/user", .fd = -1 }, |
59 | { .nstype = CLONE_NEWCGROUP,.name = "ns/cgroup", .fd = -1 }, | |
60 | { .nstype = CLONE_NEWIPC, .name = "ns/ipc", .fd = -1 }, | |
61 | { .nstype = CLONE_NEWUTS, .name = "ns/uts", .fd = -1 }, | |
62 | { .nstype = CLONE_NEWNET, .name = "ns/net", .fd = -1 }, | |
63 | { .nstype = CLONE_NEWPID, .name = "ns/pid", .fd = -1 }, | |
64 | { .nstype = CLONE_NEWNS, .name = "ns/mnt", .fd = -1 }, | |
9905912f | 65 | { .nstype = 0, .name = NULL, .fd = -1 } |
f8aa8e94 EB |
66 | }; |
67 | ||
fa2cd89a | 68 | static void __attribute__((__noreturn__)) usage(void) |
f8aa8e94 | 69 | { |
fa2cd89a | 70 | FILE *out = stdout; |
f8aa8e94 EB |
71 | |
72 | fputs(USAGE_HEADER, out); | |
0f0b5823 | 73 | fprintf(out, _(" %s [options] [<program> [<argument>...]]\n"), |
f8aa8e94 EB |
74 | program_invocation_short_name); |
75 | ||
451dbcfa BS |
76 | fputs(USAGE_SEPARATOR, out); |
77 | fputs(_("Run a program with namespaces of other processes.\n"), out); | |
78 | ||
f8aa8e94 | 79 | fputs(USAGE_OPTIONS, out); |
974cc006 | 80 | fputs(_(" -a, --all enter all namespaces\n"), out); |
26f879ed | 81 | fputs(_(" -t, --target <pid> target process to get namespaces from\n"), out); |
42f00a7d BS |
82 | fputs(_(" -m, --mount[=<file>] enter mount namespace\n"), out); |
83 | fputs(_(" -u, --uts[=<file>] enter UTS namespace (hostname etc)\n"), out); | |
84 | fputs(_(" -i, --ipc[=<file>] enter System V IPC namespace\n"), out); | |
85 | fputs(_(" -n, --net[=<file>] enter network namespace\n"), out); | |
86 | fputs(_(" -p, --pid[=<file>] enter pid namespace\n"), out); | |
f9e7b66d | 87 | fputs(_(" -C, --cgroup[=<file>] enter cgroup namespace\n"), out); |
42f00a7d | 88 | fputs(_(" -U, --user[=<file>] enter user namespace\n"), out); |
47f42c1d KZ |
89 | fputs(_(" -S, --setuid <uid> set uid in entered namespace\n"), out); |
90 | fputs(_(" -G, --setgid <gid> set gid in entered namespace\n"), out); | |
e99a6626 | 91 | fputs(_(" --preserve-credentials do not touch uids or gids\n"), out); |
42f00a7d BS |
92 | fputs(_(" -r, --root[=<dir>] set the root directory\n"), out); |
93 | fputs(_(" -w, --wd[=<dir>] set the working directory\n"), out); | |
26f879ed | 94 | fputs(_(" -F, --no-fork do not fork before exec'ing <program>\n"), out); |
355ee3b8 KZ |
95 | #ifdef HAVE_LIBSELINUX |
96 | fputs(_(" -Z, --follow-context set SELinux context according to --target PID\n"), out); | |
97 | #endif | |
26f879ed | 98 | |
f8aa8e94 | 99 | fputs(USAGE_SEPARATOR, out); |
f45f3ec3 RM |
100 | printf(USAGE_HELP_OPTIONS(24)); |
101 | printf(USAGE_MAN_TAIL("nsenter(1)")); | |
f8aa8e94 | 102 | |
fa2cd89a | 103 | exit(EXIT_SUCCESS); |
f8aa8e94 EB |
104 | } |
105 | ||
106 | static pid_t namespace_target_pid = 0; | |
107 | static int root_fd = -1; | |
108 | static int wd_fd = -1; | |
109 | ||
f9bbdea6 | 110 | static void open_target_fd(int *fd, const char *type, const char *path) |
f8aa8e94 EB |
111 | { |
112 | char pathbuf[PATH_MAX]; | |
113 | ||
114 | if (!path && namespace_target_pid) { | |
115 | snprintf(pathbuf, sizeof(pathbuf), "/proc/%u/%s", | |
a167328a | 116 | namespace_target_pid, type); |
f8aa8e94 EB |
117 | path = pathbuf; |
118 | } | |
119 | if (!path) | |
a167328a SK |
120 | errx(EXIT_FAILURE, |
121 | _("neither filename nor target pid supplied for %s"), | |
122 | type); | |
f8aa8e94 EB |
123 | |
124 | if (*fd >= 0) | |
125 | close(*fd); | |
126 | ||
127 | *fd = open(path, O_RDONLY); | |
128 | if (*fd < 0) | |
8b7a7750 | 129 | err(EXIT_FAILURE, _("cannot open %s"), path); |
f8aa8e94 EB |
130 | } |
131 | ||
f9bbdea6 | 132 | static void open_namespace_fd(int nstype, const char *path) |
f8aa8e94 EB |
133 | { |
134 | struct namespace_file *nsfile; | |
135 | ||
136 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) { | |
137 | if (nstype != nsfile->nstype) | |
138 | continue; | |
139 | ||
140 | open_target_fd(&nsfile->fd, nsfile->name, path); | |
141 | return; | |
142 | } | |
143 | /* This should never happen */ | |
dfd8b117 | 144 | assert(nsfile->nstype); |
f8aa8e94 EB |
145 | } |
146 | ||
974cc006 KZ |
147 | static int get_ns_ino(const char *path, ino_t *ino) |
148 | { | |
149 | struct stat st; | |
150 | ||
151 | if (stat(path, &st) != 0) | |
152 | return -errno; | |
153 | *ino = st.st_ino; | |
154 | return 0; | |
155 | } | |
156 | ||
157 | static int is_same_namespace(pid_t a, pid_t b, const char *type) | |
158 | { | |
159 | char path[PATH_MAX]; | |
b9e4ee42 | 160 | ino_t a_ino = 0, b_ino = 0; |
974cc006 KZ |
161 | |
162 | snprintf(path, sizeof(path), "/proc/%u/%s", a, type); | |
163 | if (get_ns_ino(path, &a_ino) != 0) | |
164 | err(EXIT_FAILURE, _("stat of %s failed"), path); | |
165 | ||
166 | snprintf(path, sizeof(path), "/proc/%u/%s", b, type); | |
167 | if (get_ns_ino(path, &b_ino) != 0) | |
168 | err(EXIT_FAILURE, _("stat of %s failed"), path); | |
169 | ||
170 | return a_ino == b_ino; | |
171 | } | |
172 | ||
c9515f86 EB |
173 | static void continue_as_child(void) |
174 | { | |
175 | pid_t child = fork(); | |
176 | int status; | |
177 | pid_t ret; | |
178 | ||
179 | if (child < 0) | |
180 | err(EXIT_FAILURE, _("fork failed")); | |
181 | ||
182 | /* Only the child returns */ | |
183 | if (child == 0) | |
184 | return; | |
185 | ||
186 | for (;;) { | |
187 | ret = waitpid(child, &status, WUNTRACED); | |
188 | if ((ret == child) && (WIFSTOPPED(status))) { | |
189 | /* The child suspended so suspend us as well */ | |
190 | kill(getpid(), SIGSTOP); | |
191 | kill(child, SIGCONT); | |
192 | } else { | |
193 | break; | |
194 | } | |
195 | } | |
196 | /* Return the child's exit code if possible */ | |
197 | if (WIFEXITED(status)) { | |
198 | exit(WEXITSTATUS(status)); | |
a167328a | 199 | } else if (WIFSIGNALED(status)) { |
c9515f86 EB |
200 | kill(getpid(), WTERMSIG(status)); |
201 | } | |
202 | exit(EXIT_FAILURE); | |
203 | } | |
204 | ||
f8aa8e94 EB |
205 | int main(int argc, char *argv[]) |
206 | { | |
e99a6626 KZ |
207 | enum { |
208 | OPT_PRESERVE_CRED = CHAR_MAX + 1 | |
209 | }; | |
f8aa8e94 | 210 | static const struct option longopts[] = { |
974cc006 | 211 | { "all", no_argument, NULL, 'a' }, |
f8aa8e94 EB |
212 | { "help", no_argument, NULL, 'h' }, |
213 | { "version", no_argument, NULL, 'V'}, | |
214 | { "target", required_argument, NULL, 't' }, | |
215 | { "mount", optional_argument, NULL, 'm' }, | |
216 | { "uts", optional_argument, NULL, 'u' }, | |
217 | { "ipc", optional_argument, NULL, 'i' }, | |
218 | { "net", optional_argument, NULL, 'n' }, | |
219 | { "pid", optional_argument, NULL, 'p' }, | |
220 | { "user", optional_argument, NULL, 'U' }, | |
f9e7b66d | 221 | { "cgroup", optional_argument, NULL, 'C' }, |
6b9e5bf6 RW |
222 | { "setuid", required_argument, NULL, 'S' }, |
223 | { "setgid", required_argument, NULL, 'G' }, | |
f8aa8e94 EB |
224 | { "root", optional_argument, NULL, 'r' }, |
225 | { "wd", optional_argument, NULL, 'w' }, | |
28384adc | 226 | { "no-fork", no_argument, NULL, 'F' }, |
e99a6626 | 227 | { "preserve-credentials", no_argument, NULL, OPT_PRESERVE_CRED }, |
355ee3b8 KZ |
228 | #ifdef HAVE_LIBSELINUX |
229 | { "follow-context", no_argument, NULL, 'Z' }, | |
230 | #endif | |
f8aa8e94 EB |
231 | { NULL, 0, NULL, 0 } |
232 | }; | |
233 | ||
234 | struct namespace_file *nsfile; | |
854d0fef | 235 | int c, pass, namespaces = 0, setgroups_nerrs = 0, preserve_cred = 0; |
47f42c1d | 236 | bool do_rd = false, do_wd = false, force_uid = false, force_gid = false; |
974cc006 | 237 | bool do_all = false; |
57dbcf94 | 238 | int do_fork = -1; /* unknown yet */ |
6b9e5bf6 RW |
239 | uid_t uid = 0; |
240 | gid_t gid = 0; | |
355ee3b8 KZ |
241 | #ifdef HAVE_LIBSELINUX |
242 | bool selinux = 0; | |
243 | #endif | |
f8aa8e94 | 244 | |
999ac5e2 | 245 | setlocale(LC_ALL, ""); |
f8aa8e94 EB |
246 | bindtextdomain(PACKAGE, LOCALEDIR); |
247 | textdomain(PACKAGE); | |
2c308875 | 248 | close_stdout_atexit(); |
f8aa8e94 | 249 | |
28384adc | 250 | while ((c = |
974cc006 | 251 | getopt_long(argc, argv, "+ahVt:m::u::i::n::p::C::U::S:G:r::w::FZ", |
28384adc ZJS |
252 | longopts, NULL)) != -1) { |
253 | switch (c) { | |
974cc006 KZ |
254 | case 'a': |
255 | do_all = true; | |
256 | break; | |
f8aa8e94 | 257 | case 't': |
a167328a SK |
258 | namespace_target_pid = |
259 | strtoul_or_err(optarg, _("failed to parse pid")); | |
f8aa8e94 EB |
260 | break; |
261 | case 'm': | |
984e1b7c ZJS |
262 | if (optarg) |
263 | open_namespace_fd(CLONE_NEWNS, optarg); | |
264 | else | |
265 | namespaces |= CLONE_NEWNS; | |
f8aa8e94 EB |
266 | break; |
267 | case 'u': | |
984e1b7c ZJS |
268 | if (optarg) |
269 | open_namespace_fd(CLONE_NEWUTS, optarg); | |
270 | else | |
271 | namespaces |= CLONE_NEWUTS; | |
f8aa8e94 EB |
272 | break; |
273 | case 'i': | |
984e1b7c ZJS |
274 | if (optarg) |
275 | open_namespace_fd(CLONE_NEWIPC, optarg); | |
276 | else | |
277 | namespaces |= CLONE_NEWIPC; | |
f8aa8e94 EB |
278 | break; |
279 | case 'n': | |
984e1b7c ZJS |
280 | if (optarg) |
281 | open_namespace_fd(CLONE_NEWNET, optarg); | |
282 | else | |
283 | namespaces |= CLONE_NEWNET; | |
f8aa8e94 EB |
284 | break; |
285 | case 'p': | |
984e1b7c ZJS |
286 | if (optarg) |
287 | open_namespace_fd(CLONE_NEWPID, optarg); | |
288 | else | |
289 | namespaces |= CLONE_NEWPID; | |
f8aa8e94 | 290 | break; |
f9e7b66d SH |
291 | case 'C': |
292 | if (optarg) | |
293 | open_namespace_fd(CLONE_NEWCGROUP, optarg); | |
294 | else | |
295 | namespaces |= CLONE_NEWCGROUP; | |
296 | break; | |
f8aa8e94 | 297 | case 'U': |
984e1b7c ZJS |
298 | if (optarg) |
299 | open_namespace_fd(CLONE_NEWUSER, optarg); | |
300 | else | |
301 | namespaces |= CLONE_NEWUSER; | |
f8aa8e94 | 302 | break; |
6b9e5bf6 RW |
303 | case 'S': |
304 | uid = strtoul_or_err(optarg, _("failed to parse uid")); | |
47f42c1d | 305 | force_uid = true; |
6b9e5bf6 RW |
306 | break; |
307 | case 'G': | |
308 | gid = strtoul_or_err(optarg, _("failed to parse gid")); | |
47f42c1d | 309 | force_gid = true; |
6b9e5bf6 | 310 | break; |
28384adc | 311 | case 'F': |
57dbcf94 | 312 | do_fork = 0; |
f8aa8e94 EB |
313 | break; |
314 | case 'r': | |
984e1b7c | 315 | if (optarg) |
82524a13 | 316 | open_target_fd(&root_fd, "root", optarg); |
984e1b7c ZJS |
317 | else |
318 | do_rd = true; | |
f8aa8e94 EB |
319 | break; |
320 | case 'w': | |
984e1b7c | 321 | if (optarg) |
82524a13 | 322 | open_target_fd(&wd_fd, "cwd", optarg); |
984e1b7c ZJS |
323 | else |
324 | do_wd = true; | |
f8aa8e94 | 325 | break; |
e99a6626 KZ |
326 | case OPT_PRESERVE_CRED: |
327 | preserve_cred = 1; | |
328 | break; | |
355ee3b8 KZ |
329 | #ifdef HAVE_LIBSELINUX |
330 | case 'Z': | |
331 | selinux = 1; | |
332 | break; | |
333 | #endif | |
2c308875 KZ |
334 | case 'h': |
335 | usage(); | |
336 | case 'V': | |
337 | print_version(EXIT_SUCCESS); | |
f8aa8e94 | 338 | default: |
677ec86c | 339 | errtryhelp(EXIT_FAILURE); |
f8aa8e94 EB |
340 | } |
341 | } | |
342 | ||
355ee3b8 KZ |
343 | #ifdef HAVE_LIBSELINUX |
344 | if (selinux && is_selinux_enabled() > 0) { | |
345 | char *scon = NULL; | |
346 | ||
347 | if (!namespace_target_pid) | |
348 | errx(EXIT_FAILURE, _("no target PID specified for --follow-context")); | |
349 | if (getpidcon(namespace_target_pid, &scon) < 0) | |
350 | errx(EXIT_FAILURE, _("failed to get %d SELinux context"), | |
351 | (int) namespace_target_pid); | |
352 | if (setexeccon(scon) < 0) | |
353 | errx(EXIT_FAILURE, _("failed to set exec context to '%s'"), scon); | |
354 | freecon(scon); | |
355 | } | |
356 | #endif | |
974cc006 KZ |
357 | |
358 | if (do_all) { | |
359 | if (!namespace_target_pid) | |
360 | errx(EXIT_FAILURE, _("no target PID specified for --all")); | |
361 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) { | |
362 | if (nsfile->fd >= 0) | |
363 | continue; /* namespace already specified */ | |
364 | ||
365 | /* It is not permitted to use setns(2) to reenter the caller's | |
366 | * current user namespace; see setns(2) man page for more details. | |
367 | */ | |
368 | if (nsfile->nstype & CLONE_NEWUSER | |
369 | && is_same_namespace(getpid(), namespace_target_pid, nsfile->name)) | |
370 | continue; | |
371 | ||
372 | namespaces |= nsfile->nstype; | |
373 | } | |
374 | } | |
375 | ||
984e1b7c ZJS |
376 | /* |
377 | * Open remaining namespace and directory descriptors. | |
378 | */ | |
379 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) | |
380 | if (nsfile->nstype & namespaces) | |
381 | open_namespace_fd(nsfile->nstype, NULL); | |
382 | if (do_rd) | |
383 | open_target_fd(&root_fd, "root", NULL); | |
384 | if (do_wd) | |
385 | open_target_fd(&wd_fd, "cwd", NULL); | |
386 | ||
83fb8aa5 KZ |
387 | /* |
388 | * Update namespaces variable to contain all requested namespaces | |
389 | */ | |
390 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) { | |
391 | if (nsfile->fd < 0) | |
392 | continue; | |
393 | namespaces |= nsfile->nstype; | |
394 | } | |
395 | ||
e99a6626 KZ |
396 | /* for user namespaces we always set UID and GID (default is 0) |
397 | * and clear root's groups if --preserve-credentials is no specified */ | |
398 | if ((namespaces & CLONE_NEWUSER) && !preserve_cred) { | |
399 | force_uid = true, force_gid = true; | |
400 | ||
401 | /* We call setgroups() before and after we enter user namespace, | |
402 | * let's complain only if both fail */ | |
403 | if (setgroups(0, NULL) != 0) | |
404 | setgroups_nerrs++; | |
405 | } | |
406 | ||
f8aa8e94 | 407 | /* |
854d0fef JB |
408 | * Now that we know which namespaces we want to enter, enter |
409 | * them. Do this in two passes, not entering the user | |
410 | * namespace on the first pass. So if we're deprivileging the | |
411 | * container we'll enter the user namespace last and if we're | |
9e930041 | 412 | * privileging it then we enter the user namespace first |
854d0fef | 413 | * (because the initial setns will fail). |
f8aa8e94 | 414 | */ |
854d0fef JB |
415 | for (pass = 0; pass < 2; pass ++) { |
416 | for (nsfile = namespace_files + 1 - pass; nsfile->nstype; nsfile++) { | |
417 | if (nsfile->fd < 0) | |
418 | continue; | |
419 | if (nsfile->nstype == CLONE_NEWPID && do_fork == -1) | |
420 | do_fork = 1; | |
421 | if (setns(nsfile->fd, nsfile->nstype)) { | |
422 | if (pass != 0) | |
423 | err(EXIT_FAILURE, | |
424 | _("reassociate to namespace '%s' failed"), | |
425 | nsfile->name); | |
426 | else | |
427 | continue; | |
428 | } | |
429 | ||
430 | close(nsfile->fd); | |
431 | nsfile->fd = -1; | |
432 | } | |
f8aa8e94 EB |
433 | } |
434 | ||
435 | /* Remember the current working directory if I'm not changing it */ | |
436 | if (root_fd >= 0 && wd_fd < 0) { | |
437 | wd_fd = open(".", O_RDONLY); | |
438 | if (wd_fd < 0) | |
a167328a SK |
439 | err(EXIT_FAILURE, |
440 | _("cannot open current working directory")); | |
f8aa8e94 EB |
441 | } |
442 | ||
443 | /* Change the root directory */ | |
444 | if (root_fd >= 0) { | |
445 | if (fchdir(root_fd) < 0) | |
a167328a SK |
446 | err(EXIT_FAILURE, |
447 | _("change directory by root file descriptor failed")); | |
f8aa8e94 EB |
448 | |
449 | if (chroot(".") < 0) | |
450 | err(EXIT_FAILURE, _("chroot failed")); | |
451 | ||
452 | close(root_fd); | |
453 | root_fd = -1; | |
454 | } | |
455 | ||
456 | /* Change the working directory */ | |
457 | if (wd_fd >= 0) { | |
458 | if (fchdir(wd_fd) < 0) | |
a167328a SK |
459 | err(EXIT_FAILURE, |
460 | _("change directory by working directory file descriptor failed")); | |
f8aa8e94 EB |
461 | |
462 | close(wd_fd); | |
463 | wd_fd = -1; | |
464 | } | |
465 | ||
57dbcf94 | 466 | if (do_fork == 1) |
c9515f86 | 467 | continue_as_child(); |
f8aa8e94 | 468 | |
47f42c1d | 469 | if (force_uid || force_gid) { |
e99a6626 | 470 | if (force_gid && setgroups(0, NULL) != 0 && setgroups_nerrs) /* drop supplementary groups */ |
99d7e174 | 471 | err(EXIT_FAILURE, _("setgroups failed")); |
47f42c1d | 472 | if (force_gid && setgid(gid) < 0) /* change GID */ |
6b9e5bf6 | 473 | err(EXIT_FAILURE, _("setgid failed")); |
47f42c1d | 474 | if (force_uid && setuid(uid) < 0) /* change UID */ |
99d7e174 | 475 | err(EXIT_FAILURE, _("setuid failed")); |
6b9e5bf6 RW |
476 | } |
477 | ||
57580694 ZJS |
478 | if (optind < argc) { |
479 | execvp(argv[optind], argv + optind); | |
fd777151 | 480 | errexec(argv[optind]); |
57580694 ZJS |
481 | } |
482 | exec_shell(); | |
f8aa8e94 | 483 | } |