]> git.ipfire.org Git - thirdparty/util-linux.git/blob - sys-utils/nsenter.c
Merge branch 'fix_cal_num_months' of https://github.com/JadingTsunami/util-linux
[thirdparty/util-linux.git] / sys-utils / nsenter.c
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
20 #include <dirent.h>
21 #include <errno.h>
22 #include <getopt.h>
23 #include <sched.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <stdbool.h>
27 #include <unistd.h>
28 #include <assert.h>
29 #include <sys/types.h>
30 #include <sys/wait.h>
31 #include <grp.h>
32 #include <sys/stat.h>
33
34 #ifdef HAVE_LIBSELINUX
35 # include <selinux/selinux.h>
36 #endif
37
38 #include "strutils.h"
39 #include "nls.h"
40 #include "c.h"
41 #include "closestream.h"
42 #include "namespace.h"
43 #include "exec_shell.h"
44 #include "optutils.h"
45 #include "xalloc.h"
46 #include "all-io.h"
47 #include "env.h"
48
49 static struct namespace_file {
50 int nstype;
51 const char *name;
52 int fd;
53 } namespace_files[] = {
54 /* Careful the order is significant in this array.
55 *
56 * The user namespace comes either first or last: first if
57 * you're using it to increase your privilege and last if
58 * you're using it to decrease. We enter the namespaces in
59 * two passes starting initially from offset 1 and then offset
60 * 0 if that fails.
61 */
62 { .nstype = CLONE_NEWUSER, .name = "ns/user", .fd = -1 },
63 { .nstype = CLONE_NEWCGROUP,.name = "ns/cgroup", .fd = -1 },
64 { .nstype = CLONE_NEWIPC, .name = "ns/ipc", .fd = -1 },
65 { .nstype = CLONE_NEWUTS, .name = "ns/uts", .fd = -1 },
66 { .nstype = CLONE_NEWNET, .name = "ns/net", .fd = -1 },
67 { .nstype = CLONE_NEWPID, .name = "ns/pid", .fd = -1 },
68 { .nstype = CLONE_NEWNS, .name = "ns/mnt", .fd = -1 },
69 { .nstype = CLONE_NEWTIME, .name = "ns/time", .fd = -1 },
70 { .nstype = 0, .name = NULL, .fd = -1 }
71 };
72
73 static void __attribute__((__noreturn__)) usage(void)
74 {
75 FILE *out = stdout;
76
77 fputs(USAGE_HEADER, out);
78 fprintf(out, _(" %s [options] [<program> [<argument>...]]\n"),
79 program_invocation_short_name);
80
81 fputs(USAGE_SEPARATOR, out);
82 fputs(_("Run a program with namespaces of other processes.\n"), out);
83
84 fputs(USAGE_OPTIONS, out);
85 fputs(_(" -a, --all enter all namespaces\n"), out);
86 fputs(_(" -t, --target <pid> target process to get namespaces from\n"), out);
87 fputs(_(" -m, --mount[=<file>] enter mount namespace\n"), out);
88 fputs(_(" -u, --uts[=<file>] enter UTS namespace (hostname etc)\n"), out);
89 fputs(_(" -i, --ipc[=<file>] enter System V IPC namespace\n"), out);
90 fputs(_(" -n, --net[=<file>] enter network namespace\n"), out);
91 fputs(_(" -p, --pid[=<file>] enter pid namespace\n"), out);
92 fputs(_(" -C, --cgroup[=<file>] enter cgroup namespace\n"), out);
93 fputs(_(" -U, --user[=<file>] enter user namespace\n"), out);
94 fputs(_(" -T, --time[=<file>] enter time namespace\n"), out);
95 fputs(_(" -S, --setuid[=<uid>] set uid in entered namespace\n"), out);
96 fputs(_(" -G, --setgid[=<gid>] set gid in entered namespace\n"), out);
97 fputs(_(" --preserve-credentials do not touch uids or gids\n"), out);
98 fputs(_(" -r, --root[=<dir>] set the root directory\n"), out);
99 fputs(_(" -w, --wd[=<dir>] set the working directory\n"), out);
100 fputs(_(" -W, --wdns <dir> set the working directory in namespace\n"), out);
101 fputs(_(" -e, --env inherit environment variables from target process\n"), out);
102 fputs(_(" -F, --no-fork do not fork before exec'ing <program>\n"), out);
103 #ifdef HAVE_LIBSELINUX
104 fputs(_(" -Z, --follow-context set SELinux context according to --target PID\n"), out);
105 #endif
106
107 fputs(USAGE_SEPARATOR, out);
108 printf(USAGE_HELP_OPTIONS(24));
109 printf(USAGE_MAN_TAIL("nsenter(1)"));
110
111 exit(EXIT_SUCCESS);
112 }
113
114 static pid_t namespace_target_pid = 0;
115 static int root_fd = -1;
116 static int wd_fd = -1;
117 static int env_fd = -1;
118 static int uid_gid_fd = -1;
119
120 static void open_target_fd(int *fd, const char *type, const char *path)
121 {
122 char pathbuf[PATH_MAX];
123
124 if (!path && namespace_target_pid) {
125 snprintf(pathbuf, sizeof(pathbuf), "/proc/%u/%s",
126 namespace_target_pid, type);
127 path = pathbuf;
128 }
129 if (!path)
130 errx(EXIT_FAILURE,
131 _("neither filename nor target pid supplied for %s"),
132 type);
133
134 if (*fd >= 0)
135 close(*fd);
136
137 *fd = open(path, O_RDONLY);
138 if (*fd < 0)
139 err(EXIT_FAILURE, _("cannot open %s"), path);
140 }
141
142 static void open_namespace_fd(int nstype, const char *path)
143 {
144 struct namespace_file *nsfile;
145
146 for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
147 if (nstype != nsfile->nstype)
148 continue;
149
150 open_target_fd(&nsfile->fd, nsfile->name, path);
151 return;
152 }
153 /* This should never happen */
154 assert(nsfile->nstype);
155 }
156
157 static int get_ns_ino(const char *path, ino_t *ino)
158 {
159 struct stat st;
160
161 if (stat(path, &st) != 0)
162 return -errno;
163 *ino = st.st_ino;
164 return 0;
165 }
166
167 static int is_usable_namespace(pid_t target, const struct namespace_file *nsfile)
168 {
169 char path[PATH_MAX];
170 ino_t my_ino = 0;
171 int rc;
172
173 /* Check NS accessibility */
174 snprintf(path, sizeof(path), "/proc/%u/%s", getpid(), nsfile->name);
175 rc = get_ns_ino(path, &my_ino);
176 if (rc == -ENOENT)
177 return false; /* Unsupported NS */
178
179 /* It is not permitted to use setns(2) to reenter the caller's
180 * current user namespace; see setns(2) man page for more details.
181 */
182 if (nsfile->nstype & CLONE_NEWUSER) {
183 ino_t target_ino = 0;
184
185 snprintf(path, sizeof(path), "/proc/%u/%s", target, nsfile->name);
186 if (get_ns_ino(path, &target_ino) != 0)
187 err(EXIT_FAILURE, _("stat of %s failed"), path);
188
189 if (my_ino == target_ino)
190 return false;
191 }
192
193 return true; /* All pass */
194 }
195
196 static void continue_as_child(void)
197 {
198 pid_t child;
199 int status;
200 pid_t ret;
201
202 /* Clear any inherited settings */
203 signal(SIGCHLD, SIG_DFL);
204
205 child = fork();
206 if (child < 0)
207 err(EXIT_FAILURE, _("fork failed"));
208
209 /* Only the child returns */
210 if (child == 0)
211 return;
212
213 for (;;) {
214 ret = waitpid(child, &status, WUNTRACED);
215 if ((ret == child) && (WIFSTOPPED(status))) {
216 /* The child suspended so suspend us as well */
217 kill(getpid(), SIGSTOP);
218 kill(child, SIGCONT);
219 } else {
220 break;
221 }
222 }
223 /* Return the child's exit code if possible */
224 if (WIFEXITED(status)) {
225 exit(WEXITSTATUS(status));
226 } else if (WIFSIGNALED(status)) {
227 kill(getpid(), WTERMSIG(status));
228 }
229 exit(EXIT_FAILURE);
230 }
231
232 int main(int argc, char *argv[])
233 {
234 enum {
235 OPT_PRESERVE_CRED = CHAR_MAX + 1
236 };
237 static const struct option longopts[] = {
238 { "all", no_argument, NULL, 'a' },
239 { "help", no_argument, NULL, 'h' },
240 { "version", no_argument, NULL, 'V'},
241 { "target", required_argument, NULL, 't' },
242 { "mount", optional_argument, NULL, 'm' },
243 { "uts", optional_argument, NULL, 'u' },
244 { "ipc", optional_argument, NULL, 'i' },
245 { "net", optional_argument, NULL, 'n' },
246 { "pid", optional_argument, NULL, 'p' },
247 { "user", optional_argument, NULL, 'U' },
248 { "cgroup", optional_argument, NULL, 'C' },
249 { "time", optional_argument, NULL, 'T' },
250 { "setuid", optional_argument, NULL, 'S' },
251 { "setgid", optional_argument, NULL, 'G' },
252 { "root", optional_argument, NULL, 'r' },
253 { "wd", optional_argument, NULL, 'w' },
254 { "wdns", optional_argument, NULL, 'W' },
255 { "env", no_argument, NULL, 'e' },
256 { "no-fork", no_argument, NULL, 'F' },
257 { "preserve-credentials", no_argument, NULL, OPT_PRESERVE_CRED },
258 #ifdef HAVE_LIBSELINUX
259 { "follow-context", no_argument, NULL, 'Z' },
260 #endif
261 { NULL, 0, NULL, 0 }
262 };
263 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
264 { 'W', 'w' },
265 { 0 }
266 };
267 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
268
269 struct namespace_file *nsfile;
270 int c, pass, namespaces = 0, setgroups_nerrs = 0, preserve_cred = 0;
271 bool do_rd = false, do_wd = false, do_uid = false, force_uid = false,
272 do_gid = false, force_gid = false, do_env = false, do_all = false;
273 int do_fork = -1; /* unknown yet */
274 char *wdns = NULL;
275 uid_t uid = 0;
276 gid_t gid = 0;
277 struct ul_env_list *envls;
278 #ifdef HAVE_LIBSELINUX
279 bool selinux = 0;
280 #endif
281
282 setlocale(LC_ALL, "");
283 bindtextdomain(PACKAGE, LOCALEDIR);
284 textdomain(PACKAGE);
285 close_stdout_atexit();
286
287 while ((c =
288 getopt_long(argc, argv, "+ahVt:m::u::i::n::p::C::U::T::S::G::r::w::W::eFZ",
289 longopts, NULL)) != -1) {
290
291 err_exclusive_options(c, longopts, excl, excl_st);
292
293 switch (c) {
294 case 'a':
295 do_all = true;
296 break;
297 case 't':
298 namespace_target_pid =
299 strtoul_or_err(optarg, _("failed to parse pid"));
300 break;
301 case 'm':
302 if (optarg)
303 open_namespace_fd(CLONE_NEWNS, optarg);
304 else
305 namespaces |= CLONE_NEWNS;
306 break;
307 case 'u':
308 if (optarg)
309 open_namespace_fd(CLONE_NEWUTS, optarg);
310 else
311 namespaces |= CLONE_NEWUTS;
312 break;
313 case 'i':
314 if (optarg)
315 open_namespace_fd(CLONE_NEWIPC, optarg);
316 else
317 namespaces |= CLONE_NEWIPC;
318 break;
319 case 'n':
320 if (optarg)
321 open_namespace_fd(CLONE_NEWNET, optarg);
322 else
323 namespaces |= CLONE_NEWNET;
324 break;
325 case 'p':
326 if (optarg)
327 open_namespace_fd(CLONE_NEWPID, optarg);
328 else
329 namespaces |= CLONE_NEWPID;
330 break;
331 case 'C':
332 if (optarg)
333 open_namespace_fd(CLONE_NEWCGROUP, optarg);
334 else
335 namespaces |= CLONE_NEWCGROUP;
336 break;
337 case 'U':
338 if (optarg)
339 open_namespace_fd(CLONE_NEWUSER, optarg);
340 else
341 namespaces |= CLONE_NEWUSER;
342 break;
343 case 'T':
344 if (optarg)
345 open_namespace_fd(CLONE_NEWTIME, optarg);
346 else
347 namespaces |= CLONE_NEWTIME;
348 break;
349 case 'S':
350 if (optarg)
351 uid = strtoul_or_err(optarg, _("failed to parse uid"));
352 else
353 do_uid = true;
354 force_uid = true;
355 break;
356 case 'G':
357 if (optarg)
358 gid = strtoul_or_err(optarg, _("failed to parse gid"));
359 else
360 do_gid = true;
361 force_gid = true;
362 break;
363 case 'F':
364 do_fork = 0;
365 break;
366 case 'r':
367 if (optarg)
368 open_target_fd(&root_fd, "root", optarg);
369 else
370 do_rd = true;
371 break;
372 case 'w':
373 if (optarg)
374 open_target_fd(&wd_fd, "cwd", optarg);
375 else
376 do_wd = true;
377 break;
378 case 'W':
379 wdns = optarg;
380 break;
381 case 'e':
382 do_env = true;
383 break;
384 case OPT_PRESERVE_CRED:
385 preserve_cred = 1;
386 break;
387 #ifdef HAVE_LIBSELINUX
388 case 'Z':
389 selinux = 1;
390 break;
391 #endif
392 case 'h':
393 usage();
394 case 'V':
395 print_version(EXIT_SUCCESS);
396 default:
397 errtryhelp(EXIT_FAILURE);
398 }
399 }
400
401 #ifdef HAVE_LIBSELINUX
402 if (selinux && is_selinux_enabled() > 0) {
403 char *scon = NULL;
404
405 if (!namespace_target_pid)
406 errx(EXIT_FAILURE, _("no target PID specified for --follow-context"));
407 if (getpidcon(namespace_target_pid, &scon) < 0)
408 errx(EXIT_FAILURE, _("failed to get %d SELinux context"),
409 (int) namespace_target_pid);
410 if (setexeccon(scon) < 0)
411 errx(EXIT_FAILURE, _("failed to set exec context to '%s'"), scon);
412 freecon(scon);
413 }
414 #endif
415
416 if (do_all) {
417 if (!namespace_target_pid)
418 errx(EXIT_FAILURE, _("no target PID specified for --all"));
419 for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
420 if (nsfile->fd >= 0)
421 continue; /* namespace already specified */
422
423 if (!is_usable_namespace(namespace_target_pid, nsfile))
424 continue;
425
426 namespaces |= nsfile->nstype;
427 }
428 }
429
430 /*
431 * Open remaining namespace and directory descriptors.
432 */
433 for (nsfile = namespace_files; nsfile->nstype; nsfile++)
434 if (nsfile->nstype & namespaces)
435 open_namespace_fd(nsfile->nstype, NULL);
436 if (do_rd)
437 open_target_fd(&root_fd, "root", NULL);
438 if (do_wd)
439 open_target_fd(&wd_fd, "cwd", NULL);
440 if (do_env)
441 open_target_fd(&env_fd, "environ", NULL);
442 if (do_uid || do_gid)
443 open_target_fd(&uid_gid_fd, "", NULL);
444
445 /*
446 * Update namespaces variable to contain all requested namespaces
447 */
448 for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
449 if (nsfile->fd < 0)
450 continue;
451 namespaces |= nsfile->nstype;
452 }
453
454 /* for user namespaces we always set UID and GID (default is 0)
455 * and clear root's groups if --preserve-credentials is no specified */
456 if ((namespaces & CLONE_NEWUSER) && !preserve_cred) {
457 force_uid = true, force_gid = true;
458
459 /* We call setgroups() before and after we enter user namespace,
460 * let's complain only if both fail */
461 if (setgroups(0, NULL) != 0)
462 setgroups_nerrs++;
463 }
464
465 /*
466 * Now that we know which namespaces we want to enter, enter
467 * them. Do this in two passes, not entering the user
468 * namespace on the first pass. So if we're deprivileging the
469 * container we'll enter the user namespace last and if we're
470 * privileging it then we enter the user namespace first
471 * (because the initial setns will fail).
472 */
473 for (pass = 0; pass < 2; pass ++) {
474 for (nsfile = namespace_files + 1 - pass; nsfile->nstype; nsfile++) {
475 if (nsfile->fd < 0)
476 continue;
477 if (nsfile->nstype == CLONE_NEWPID && do_fork == -1)
478 do_fork = 1;
479 if (setns(nsfile->fd, nsfile->nstype)) {
480 if (pass != 0)
481 err(EXIT_FAILURE,
482 _("reassociate to namespace '%s' failed"),
483 nsfile->name);
484 else
485 continue;
486 }
487
488 close(nsfile->fd);
489 nsfile->fd = -1;
490 }
491 }
492
493 /* Remember the current working directory if I'm not changing it */
494 if (root_fd >= 0 && wd_fd < 0 && wdns == NULL) {
495 wd_fd = open(".", O_RDONLY);
496 if (wd_fd < 0)
497 err(EXIT_FAILURE,
498 _("cannot open current working directory"));
499 }
500
501 /* Change the root directory */
502 if (root_fd >= 0) {
503 if (fchdir(root_fd) < 0)
504 err(EXIT_FAILURE,
505 _("change directory by root file descriptor failed"));
506
507 if (chroot(".") < 0)
508 err(EXIT_FAILURE, _("chroot failed"));
509 if (chdir("/"))
510 err(EXIT_FAILURE, _("cannot change directory to %s"), "/");
511
512 close(root_fd);
513 root_fd = -1;
514 }
515
516 /* working directory specified as in-namespace path */
517 if (wdns) {
518 wd_fd = open(wdns, O_RDONLY);
519 if (wd_fd < 0)
520 err(EXIT_FAILURE,
521 _("cannot open current working directory"));
522 }
523
524 /* Change the working directory */
525 if (wd_fd >= 0) {
526 if (fchdir(wd_fd) < 0)
527 err(EXIT_FAILURE,
528 _("change directory by working directory file descriptor failed"));
529
530 close(wd_fd);
531 wd_fd = -1;
532 }
533
534 /* Pass environment variables of the target process to the spawned process */
535 if (env_fd >= 0) {
536 if ((envls = env_from_fd(env_fd)) == NULL)
537 err(EXIT_FAILURE, _("failed to get environment variables"));
538 clearenv();
539 if (env_list_setenv(envls) < 0)
540 err(EXIT_FAILURE, _("failed to set environment variables"));
541 env_list_free(envls);
542 close(env_fd);
543 }
544
545 if (uid_gid_fd >= 0) {
546 struct stat st;
547
548 if (fstat(uid_gid_fd, &st) > 0)
549 err(EXIT_FAILURE, _("can not get process stat"));
550
551 close(uid_gid_fd);
552 uid_gid_fd = -1;
553
554 if (do_uid)
555 uid = st.st_uid;
556 if (do_gid)
557 gid = st.st_gid;
558 }
559
560 if (do_fork == 1)
561 continue_as_child();
562
563 if (force_uid || force_gid) {
564 if (force_gid && setgroups(0, NULL) != 0 && setgroups_nerrs) /* drop supplementary groups */
565 err(EXIT_FAILURE, _("setgroups failed"));
566 if (force_gid && setgid(gid) < 0) /* change GID */
567 err(EXIT_FAILURE, _("setgid failed"));
568 if (force_uid && setuid(uid) < 0) /* change UID */
569 err(EXIT_FAILURE, _("setuid failed"));
570 }
571
572 if (optind < argc) {
573 execvp(argv[optind], argv + optind);
574 errexec(argv[optind]);
575 }
576 exec_shell();
577 }