2 * nsenter(1) - command-line interface for setns(2)
4 * Copyright (C) 2012-2013 Eric Biederman <ebiederm@xmission.com>
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.
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.
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.
29 #include <sys/types.h>
33 #include <sys/statfs.h>
35 #include <sys/ioctl.h>
36 #ifdef HAVE_LINUX_NSFS_H
37 # include <linux/nsfs.h>
40 # define NS_GET_USERNS _IO(0xb7, 0x1)
43 #ifdef HAVE_LIBSELINUX
44 # include <selinux/selinux.h>
50 #include "closestream.h"
51 #include "namespace.h"
52 #include "exec_shell.h"
58 #include "statfs_magic.h"
59 #include "pathnames.h"
61 static struct namespace_file
{
65 } namespace_files
[] = {
66 /* Careful the order is significant in this array.
68 * The user namespace comes either first or last: first if
69 * you're using it to increase your privilege and last if
70 * you're using it to decrease. We enter the namespaces in
71 * two passes starting initially from offset 1 and then offset
74 { .nstype
= CLONE_NEWUSER
, .name
= "ns/user", .fd
= -1 },
75 { .nstype
= CLONE_NEWCGROUP
,.name
= "ns/cgroup", .fd
= -1 },
76 { .nstype
= CLONE_NEWIPC
, .name
= "ns/ipc", .fd
= -1 },
77 { .nstype
= CLONE_NEWUTS
, .name
= "ns/uts", .fd
= -1 },
78 { .nstype
= CLONE_NEWNET
, .name
= "ns/net", .fd
= -1 },
79 { .nstype
= CLONE_NEWPID
, .name
= "ns/pid", .fd
= -1 },
80 { .nstype
= CLONE_NEWNS
, .name
= "ns/mnt", .fd
= -1 },
81 { .nstype
= CLONE_NEWTIME
, .name
= "ns/time", .fd
= -1 },
82 { .nstype
= 0, .name
= NULL
, .fd
= -1 }
85 static void __attribute__((__noreturn__
)) usage(void)
89 fputs(USAGE_HEADER
, out
);
90 fprintf(out
, _(" %s [options] [<program> [<argument>...]]\n"),
91 program_invocation_short_name
);
93 fputs(USAGE_SEPARATOR
, out
);
94 fputs(_("Run a program with namespaces of other processes.\n"), out
);
96 fputs(USAGE_OPTIONS
, out
);
97 fputs(_(" -a, --all enter all namespaces\n"), out
);
98 fputs(_(" -t, --target <pid> target process to get namespaces from\n"), out
);
99 fputs(_(" -m, --mount[=<file>] enter mount namespace\n"), out
);
100 fputs(_(" -u, --uts[=<file>] enter UTS namespace (hostname etc)\n"), out
);
101 fputs(_(" -i, --ipc[=<file>] enter System V IPC namespace\n"), out
);
102 fputs(_(" -n, --net[=<file>] enter network namespace\n"), out
);
103 fputs(_(" -p, --pid[=<file>] enter pid namespace\n"), out
);
104 fputs(_(" -C, --cgroup[=<file>] enter cgroup namespace\n"), out
);
105 fputs(_(" -U, --user[=<file>] enter user namespace\n"), out
);
106 fputs(_(" --user-parent enter parent user namespace\n"), out
);
107 fputs(_(" -T, --time[=<file>] enter time namespace\n"), out
);
108 fputs(_(" -S, --setuid[=<uid>] set uid in entered namespace\n"), out
);
109 fputs(_(" -G, --setgid[=<gid>] set gid in entered namespace\n"), out
);
110 fputs(_(" --preserve-credentials do not touch uids or gids\n"), out
);
111 fputs(_(" --keep-caps retain capabilities granted in user namespaces\n"), out
);
112 fputs(_(" -r, --root[=<dir>] set the root directory\n"), out
);
113 fputs(_(" -w, --wd[=<dir>] set the working directory\n"), out
);
114 fputs(_(" -W, --wdns <dir> set the working directory in namespace\n"), out
);
115 fputs(_(" -e, --env inherit environment variables from target process\n"), out
);
116 fputs(_(" -F, --no-fork do not fork before exec'ing <program>\n"), out
);
117 fputs(_(" -c, --join-cgroup join the cgroup of the target process\n"), out
);
118 #ifdef HAVE_LIBSELINUX
119 fputs(_(" -Z, --follow-context set SELinux context according to --target PID\n"), out
);
122 fputs(USAGE_SEPARATOR
, out
);
123 fprintf(out
, USAGE_HELP_OPTIONS(24));
124 fprintf(out
, USAGE_MAN_TAIL("nsenter(1)"));
129 static pid_t namespace_target_pid
= 0;
130 static int root_fd
= -1;
131 static int wd_fd
= -1;
132 static int env_fd
= -1;
133 static int uid_gid_fd
= -1;
134 static int cgroup_procs_fd
= -1;
136 static void set_parent_user_ns_fd(void)
138 struct namespace_file
*nsfile
= NULL
;
139 struct namespace_file
*user_nsfile
= NULL
;
142 for (nsfile
= namespace_files
; nsfile
->nstype
; nsfile
++) {
143 if (nsfile
->nstype
== CLONE_NEWUSER
)
144 user_nsfile
= nsfile
;
146 if (nsfile
->fd
== -1)
149 parent_ns
= ioctl(nsfile
->fd
, NS_GET_USERNS
);
151 err(EXIT_FAILURE
, _("failed to open parent ns of %s"), nsfile
->name
);
157 errx(EXIT_FAILURE
, _("no namespaces to get parent of"));
159 user_nsfile
->fd
= parent_ns
;
163 static void open_target_fd(int *fd
, const char *type
, const char *path
)
165 char pathbuf
[PATH_MAX
];
167 if (!path
&& namespace_target_pid
) {
168 snprintf(pathbuf
, sizeof(pathbuf
), "/proc/%u/%s",
169 namespace_target_pid
, type
);
174 _("neither filename nor target pid supplied for %s"),
180 *fd
= open(path
, O_RDONLY
);
182 err(EXIT_FAILURE
, _("cannot open %s"), path
);
185 static void open_namespace_fd(int nstype
, const char *path
)
187 struct namespace_file
*nsfile
;
189 for (nsfile
= namespace_files
; nsfile
->nstype
; nsfile
++) {
190 if (nstype
!= nsfile
->nstype
)
193 open_target_fd(&nsfile
->fd
, nsfile
->name
, path
);
196 /* This should never happen */
197 assert(nsfile
->nstype
);
200 static int get_ns_ino(const char *path
, ino_t
*ino
)
204 if (stat(path
, &st
) != 0)
210 static void open_cgroup_procs(void)
212 char *buf
= NULL
, *path
= NULL
, *p
;
214 char fdpath
[PATH_MAX
];
216 open_target_fd(&cgroup_fd
, "cgroup", optarg
);
218 if (read_all_alloc(cgroup_fd
, &buf
) < 1)
219 err(EXIT_FAILURE
, _("failed to get cgroup path"));
221 p
= strtok(buf
, "\n");
223 path
= strrchr(p
, ':');
225 err(EXIT_FAILURE
, _("failed to get cgroup path"));
228 snprintf(fdpath
, sizeof(fdpath
), _PATH_SYS_CGROUP
"/%s/cgroup.procs", path
);
230 if ((cgroup_procs_fd
= open(fdpath
, O_WRONLY
| O_APPEND
)) < 0)
231 err(EXIT_FAILURE
, _("failed to open cgroup.procs"));
236 static int is_cgroup2(void)
238 struct statfs fs_stat
;
241 rc
= statfs(_PATH_SYS_CGROUP
, &fs_stat
);
243 err(EXIT_FAILURE
, _("statfs %s failed"), _PATH_SYS_CGROUP
);
244 return F_TYPE_EQUAL(fs_stat
.f_type
, STATFS_CGROUP2_MAGIC
);
247 static void join_into_cgroup(void)
250 char buf
[ sizeof(stringify_value(UINT32_MAX
)) ];
254 len
= snprintf(buf
, sizeof(buf
), "%zu", (size_t) pid
);
255 if (write_all(cgroup_procs_fd
, buf
, len
))
256 err(EXIT_FAILURE
, _("write cgroup.procs failed"));
259 static int is_usable_namespace(pid_t target
, const struct namespace_file
*nsfile
)
265 /* Check NS accessibility */
266 snprintf(path
, sizeof(path
), "/proc/%u/%s", getpid(), nsfile
->name
);
267 rc
= get_ns_ino(path
, &my_ino
);
269 return false; /* Unsupported NS */
271 /* It is not permitted to use setns(2) to reenter the caller's
272 * current user namespace; see setns(2) man page for more details.
274 if (nsfile
->nstype
& CLONE_NEWUSER
) {
275 ino_t target_ino
= 0;
277 snprintf(path
, sizeof(path
), "/proc/%u/%s", target
, nsfile
->name
);
278 if (get_ns_ino(path
, &target_ino
) != 0)
279 err(EXIT_FAILURE
, _("stat of %s failed"), path
);
281 if (my_ino
== target_ino
)
285 return true; /* All pass */
288 static void continue_as_child(void)
294 /* Clear any inherited settings */
295 signal(SIGCHLD
, SIG_DFL
);
299 err(EXIT_FAILURE
, _("fork failed"));
301 /* Only the child returns */
306 ret
= waitpid(child
, &status
, WUNTRACED
);
307 if ((ret
== child
) && (WIFSTOPPED(status
))) {
308 /* The child suspended so suspend us as well */
309 kill(getpid(), SIGSTOP
);
310 kill(child
, SIGCONT
);
315 /* Return the child's exit code if possible */
316 if (WIFEXITED(status
)) {
317 exit(WEXITSTATUS(status
));
318 } else if (WIFSIGNALED(status
)) {
319 kill(getpid(), WTERMSIG(status
));
324 int main(int argc
, char *argv
[])
327 OPT_PRESERVE_CRED
= CHAR_MAX
+ 1,
331 static const struct option longopts
[] = {
332 { "all", no_argument
, NULL
, 'a' },
333 { "help", no_argument
, NULL
, 'h' },
334 { "version", no_argument
, NULL
, 'V'},
335 { "target", required_argument
, NULL
, 't' },
336 { "mount", optional_argument
, NULL
, 'm' },
337 { "uts", optional_argument
, NULL
, 'u' },
338 { "ipc", optional_argument
, NULL
, 'i' },
339 { "net", optional_argument
, NULL
, 'n' },
340 { "pid", optional_argument
, NULL
, 'p' },
341 { "user", optional_argument
, NULL
, 'U' },
342 { "cgroup", optional_argument
, NULL
, 'C' },
343 { "time", optional_argument
, NULL
, 'T' },
344 { "setuid", required_argument
, NULL
, 'S' },
345 { "setgid", required_argument
, NULL
, 'G' },
346 { "root", optional_argument
, NULL
, 'r' },
347 { "wd", optional_argument
, NULL
, 'w' },
348 { "wdns", optional_argument
, NULL
, 'W' },
349 { "env", no_argument
, NULL
, 'e' },
350 { "no-fork", no_argument
, NULL
, 'F' },
351 { "join-cgroup", no_argument
, NULL
, 'c'},
352 { "preserve-credentials", no_argument
, NULL
, OPT_PRESERVE_CRED
},
353 { "keep-caps", no_argument
, NULL
, OPT_KEEPCAPS
},
354 { "user-parent", no_argument
, NULL
, OPT_USER_PARENT
},
355 #ifdef HAVE_LIBSELINUX
356 { "follow-context", no_argument
, NULL
, 'Z' },
360 static const ul_excl_t excl
[] = { /* rows and cols in ASCII order */
364 int excl_st
[ARRAY_SIZE(excl
)] = UL_EXCL_STATUS_INIT
;
366 struct namespace_file
*nsfile
;
367 int c
, pass
, namespaces
= 0, setgroups_nerrs
= 0, preserve_cred
= 0;
368 bool do_rd
= false, do_wd
= false, do_uid
= false, force_uid
= false,
369 do_gid
= false, force_gid
= false, do_env
= false, do_all
= false,
370 do_join_cgroup
= false, do_user_parent
= false;
371 int do_fork
= -1; /* unknown yet */
376 struct ul_env_list
*envls
;
377 #ifdef HAVE_LIBSELINUX
381 setlocale(LC_ALL
, "");
382 bindtextdomain(PACKAGE
, LOCALEDIR
);
384 close_stdout_atexit();
387 getopt_long(argc
, argv
, "+ahVt:m::u::i::n::p::C::U::T::S:G:r::w::W::ecFZ",
388 longopts
, NULL
)) != -1) {
390 err_exclusive_options(c
, longopts
, excl
, excl_st
);
397 namespace_target_pid
=
398 strtoul_or_err(optarg
, _("failed to parse pid"));
402 open_namespace_fd(CLONE_NEWNS
, optarg
);
404 namespaces
|= CLONE_NEWNS
;
408 open_namespace_fd(CLONE_NEWUTS
, optarg
);
410 namespaces
|= CLONE_NEWUTS
;
414 open_namespace_fd(CLONE_NEWIPC
, optarg
);
416 namespaces
|= CLONE_NEWIPC
;
420 open_namespace_fd(CLONE_NEWNET
, optarg
);
422 namespaces
|= CLONE_NEWNET
;
426 open_namespace_fd(CLONE_NEWPID
, optarg
);
428 namespaces
|= CLONE_NEWPID
;
432 open_namespace_fd(CLONE_NEWCGROUP
, optarg
);
434 namespaces
|= CLONE_NEWCGROUP
;
438 open_namespace_fd(CLONE_NEWUSER
, optarg
);
440 namespaces
|= CLONE_NEWUSER
;
444 open_namespace_fd(CLONE_NEWTIME
, optarg
);
446 namespaces
|= CLONE_NEWTIME
;
449 if (strcmp(optarg
, "follow") == 0)
452 uid
= strtoul_or_err(optarg
, _("failed to parse uid"));
456 if (strcmp(optarg
, "follow") == 0)
459 gid
= strtoul_or_err(optarg
, _("failed to parse gid"));
466 do_join_cgroup
= true;
470 open_target_fd(&root_fd
, "root", optarg
);
476 open_target_fd(&wd_fd
, "cwd", optarg
);
486 case OPT_PRESERVE_CRED
:
492 case OPT_USER_PARENT
:
493 do_user_parent
= true;
495 #ifdef HAVE_LIBSELINUX
503 print_version(EXIT_SUCCESS
);
505 errtryhelp(EXIT_FAILURE
);
509 #ifdef HAVE_LIBSELINUX
510 if (selinux
&& is_selinux_enabled() > 0) {
513 if (!namespace_target_pid
)
514 errx(EXIT_FAILURE
, _("no target PID specified for --follow-context"));
515 if (getpidcon(namespace_target_pid
, &scon
) < 0)
516 errx(EXIT_FAILURE
, _("failed to get %d SELinux context"),
517 (int) namespace_target_pid
);
518 if (setexeccon(scon
) < 0)
519 errx(EXIT_FAILURE
, _("failed to set exec context to '%s'"), scon
);
525 if (!namespace_target_pid
)
526 errx(EXIT_FAILURE
, _("no target PID specified for --all"));
527 for (nsfile
= namespace_files
; nsfile
->nstype
; nsfile
++) {
529 continue; /* namespace already specified */
531 if (!is_usable_namespace(namespace_target_pid
, nsfile
))
534 namespaces
|= nsfile
->nstype
;
539 * Open remaining namespace and directory descriptors.
541 for (nsfile
= namespace_files
; nsfile
->nstype
; nsfile
++)
542 if (nsfile
->nstype
& namespaces
)
543 open_namespace_fd(nsfile
->nstype
, NULL
);
545 open_target_fd(&root_fd
, "root", NULL
);
547 open_target_fd(&wd_fd
, "cwd", NULL
);
549 open_target_fd(&env_fd
, "environ", NULL
);
550 if (do_uid
|| do_gid
)
551 open_target_fd(&uid_gid_fd
, "", NULL
);
552 if (do_join_cgroup
) {
554 errx(EXIT_FAILURE
, _("--join-cgroup is only supported in cgroup v2"));
559 * Get parent userns from any available ns.
562 set_parent_user_ns_fd();
565 * Update namespaces variable to contain all requested namespaces
567 for (nsfile
= namespace_files
; nsfile
->nstype
; nsfile
++) {
570 namespaces
|= nsfile
->nstype
;
573 /* for user namespaces we always set UID and GID (default is 0)
574 * and clear root's groups if --preserve-credentials is no specified */
575 if ((namespaces
& CLONE_NEWUSER
) && !preserve_cred
) {
576 force_uid
= true, force_gid
= true;
578 /* We call setgroups() before and after we enter user namespace,
579 * let's complain only if both fail */
580 if (setgroups(0, NULL
) != 0)
585 * Now that we know which namespaces we want to enter, enter
586 * them. Do this in two passes, not entering the user
587 * namespace on the first pass. So if we're deprivileging the
588 * container we'll enter the user namespace last and if we're
589 * privileging it then we enter the user namespace first
590 * (because the initial setns will fail).
592 for (pass
= 0; pass
< 2; pass
++) {
593 for (nsfile
= namespace_files
+ 1 - pass
; nsfile
->nstype
; nsfile
++) {
596 if (nsfile
->nstype
== CLONE_NEWPID
&& do_fork
== -1)
598 if (setns(nsfile
->fd
, nsfile
->nstype
)) {
601 _("reassociate to namespace '%s' failed"),
612 /* Remember the current working directory if I'm not changing it */
613 if (root_fd
>= 0 && wd_fd
< 0 && wdns
== NULL
) {
614 wd_fd
= open(".", O_RDONLY
);
617 _("cannot open current working directory"));
620 /* Change the root directory */
622 if (fchdir(root_fd
) < 0)
624 _("change directory by root file descriptor failed"));
627 err(EXIT_FAILURE
, _("chroot failed"));
629 err(EXIT_FAILURE
, _("cannot change directory to %s"), "/");
635 /* working directory specified as in-namespace path */
637 wd_fd
= open(wdns
, O_RDONLY
);
640 _("cannot open current working directory"));
643 /* Change the working directory */
645 if (fchdir(wd_fd
) < 0)
647 _("change directory by working directory file descriptor failed"));
653 /* Pass environment variables of the target process to the spawned process */
655 if ((envls
= env_from_fd(env_fd
)) == NULL
)
656 err(EXIT_FAILURE
, _("failed to get environment variables"));
658 if (env_list_setenv(envls
) < 0)
659 err(EXIT_FAILURE
, _("failed to set environment variables"));
660 env_list_free(envls
);
664 // Join into the target cgroup
665 if (cgroup_procs_fd
>= 0)
668 if (uid_gid_fd
>= 0) {
671 if (fstat(uid_gid_fd
, &st
) > 0)
672 err(EXIT_FAILURE
, _("can not get process stat"));
686 if (force_uid
|| force_gid
) {
687 if (force_gid
&& setgroups(0, NULL
) != 0 && setgroups_nerrs
) /* drop supplementary groups */
688 err(EXIT_FAILURE
, _("setgroups failed"));
689 if (force_gid
&& setgid(gid
) < 0) /* change GID */
690 err(EXIT_FAILURE
, _("setgid failed"));
691 if (force_uid
&& setuid(uid
) < 0) /* change UID */
692 err(EXIT_FAILURE
, _("setuid failed"));
695 if (keepcaps
&& (namespaces
& CLONE_NEWUSER
))
696 cap_permitted_to_ambient();
699 execvp(argv
[optind
], argv
+ optind
);
700 errexec(argv
[optind
]);