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>
34 #ifdef HAVE_LIBSELINUX
35 # include <selinux/selinux.h>
41 #include "closestream.h"
42 #include "namespace.h"
43 #include "exec_shell.h"
45 static struct namespace_file
{
49 } namespace_files
[] = {
50 /* Careful the order is significant in this array.
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
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 },
65 { .nstype
= 0, .name
= NULL
, .fd
= -1 }
68 static void __attribute__((__noreturn__
)) usage(int status
)
70 FILE *out
= status
== EXIT_SUCCESS
? stdout
: stderr
;
72 fputs(USAGE_HEADER
, out
);
73 fprintf(out
, _(" %s [options] [<program> [<argument>...]]\n"),
74 program_invocation_short_name
);
76 fputs(USAGE_SEPARATOR
, out
);
77 fputs(_("Run a program with namespaces of other processes.\n"), out
);
79 fputs(USAGE_OPTIONS
, out
);
80 fputs(_(" -a, --all enter all namespaces\n"), out
);
81 fputs(_(" -t, --target <pid> target process to get namespaces from\n"), out
);
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
);
87 fputs(_(" -C, --cgroup[=<file>] enter cgroup namespace\n"), out
);
88 fputs(_(" -U, --user[=<file>] enter user namespace\n"), out
);
89 fputs(_(" -S, --setuid <uid> set uid in entered namespace\n"), out
);
90 fputs(_(" -G, --setgid <gid> set gid in entered namespace\n"), out
);
91 fputs(_(" --preserve-credentials do not touch uids or gids\n"), out
);
92 fputs(_(" -r, --root[=<dir>] set the root directory\n"), out
);
93 fputs(_(" -w, --wd[=<dir>] set the working directory\n"), out
);
94 fputs(_(" -F, --no-fork do not fork before exec'ing <program>\n"), out
);
95 #ifdef HAVE_LIBSELINUX
96 fputs(_(" -Z, --follow-context set SELinux context according to --target PID\n"), out
);
99 fputs(USAGE_SEPARATOR
, out
);
100 fputs(USAGE_HELP
, out
);
101 fputs(USAGE_VERSION
, out
);
102 fprintf(out
, USAGE_MAN_TAIL("nsenter(1)"));
107 static pid_t namespace_target_pid
= 0;
108 static int root_fd
= -1;
109 static int wd_fd
= -1;
111 static void open_target_fd(int *fd
, const char *type
, const char *path
)
113 char pathbuf
[PATH_MAX
];
115 if (!path
&& namespace_target_pid
) {
116 snprintf(pathbuf
, sizeof(pathbuf
), "/proc/%u/%s",
117 namespace_target_pid
, type
);
122 _("neither filename nor target pid supplied for %s"),
128 *fd
= open(path
, O_RDONLY
);
130 err(EXIT_FAILURE
, _("cannot open %s"), path
);
133 static void open_namespace_fd(int nstype
, const char *path
)
135 struct namespace_file
*nsfile
;
137 for (nsfile
= namespace_files
; nsfile
->nstype
; nsfile
++) {
138 if (nstype
!= nsfile
->nstype
)
141 open_target_fd(&nsfile
->fd
, nsfile
->name
, path
);
144 /* This should never happen */
145 assert(nsfile
->nstype
);
148 static int get_ns_ino(const char *path
, ino_t
*ino
)
152 if (stat(path
, &st
) != 0)
158 static int is_same_namespace(pid_t a
, pid_t b
, const char *type
)
163 snprintf(path
, sizeof(path
), "/proc/%u/%s", a
, type
);
164 if (get_ns_ino(path
, &a_ino
) != 0)
165 err(EXIT_FAILURE
, _("stat of %s failed"), path
);
167 snprintf(path
, sizeof(path
), "/proc/%u/%s", b
, type
);
168 if (get_ns_ino(path
, &b_ino
) != 0)
169 err(EXIT_FAILURE
, _("stat of %s failed"), path
);
171 return a_ino
== b_ino
;
174 static void continue_as_child(void)
176 pid_t child
= fork();
181 err(EXIT_FAILURE
, _("fork failed"));
183 /* Only the child returns */
188 ret
= waitpid(child
, &status
, WUNTRACED
);
189 if ((ret
== child
) && (WIFSTOPPED(status
))) {
190 /* The child suspended so suspend us as well */
191 kill(getpid(), SIGSTOP
);
192 kill(child
, SIGCONT
);
197 /* Return the child's exit code if possible */
198 if (WIFEXITED(status
)) {
199 exit(WEXITSTATUS(status
));
200 } else if (WIFSIGNALED(status
)) {
201 kill(getpid(), WTERMSIG(status
));
206 int main(int argc
, char *argv
[])
209 OPT_PRESERVE_CRED
= CHAR_MAX
+ 1
211 static const struct option longopts
[] = {
212 { "all", no_argument
, NULL
, 'a' },
213 { "help", no_argument
, NULL
, 'h' },
214 { "version", no_argument
, NULL
, 'V'},
215 { "target", required_argument
, NULL
, 't' },
216 { "mount", optional_argument
, NULL
, 'm' },
217 { "uts", optional_argument
, NULL
, 'u' },
218 { "ipc", optional_argument
, NULL
, 'i' },
219 { "net", optional_argument
, NULL
, 'n' },
220 { "pid", optional_argument
, NULL
, 'p' },
221 { "user", optional_argument
, NULL
, 'U' },
222 { "cgroup", optional_argument
, NULL
, 'C' },
223 { "setuid", required_argument
, NULL
, 'S' },
224 { "setgid", required_argument
, NULL
, 'G' },
225 { "root", optional_argument
, NULL
, 'r' },
226 { "wd", optional_argument
, NULL
, 'w' },
227 { "no-fork", no_argument
, NULL
, 'F' },
228 { "preserve-credentials", no_argument
, NULL
, OPT_PRESERVE_CRED
},
229 #ifdef HAVE_LIBSELINUX
230 { "follow-context", no_argument
, NULL
, 'Z' },
235 struct namespace_file
*nsfile
;
236 int c
, pass
, namespaces
= 0, setgroups_nerrs
= 0, preserve_cred
= 0;
237 bool do_rd
= false, do_wd
= false, force_uid
= false, force_gid
= false;
239 int do_fork
= -1; /* unknown yet */
242 #ifdef HAVE_LIBSELINUX
246 setlocale(LC_ALL
, "");
247 bindtextdomain(PACKAGE
, LOCALEDIR
);
249 atexit(close_stdout
);
252 getopt_long(argc
, argv
, "+ahVt:m::u::i::n::p::C::U::S:G:r::w::FZ",
253 longopts
, NULL
)) != -1) {
258 printf(UTIL_LINUX_VERSION
);
264 namespace_target_pid
=
265 strtoul_or_err(optarg
, _("failed to parse pid"));
269 open_namespace_fd(CLONE_NEWNS
, optarg
);
271 namespaces
|= CLONE_NEWNS
;
275 open_namespace_fd(CLONE_NEWUTS
, optarg
);
277 namespaces
|= CLONE_NEWUTS
;
281 open_namespace_fd(CLONE_NEWIPC
, optarg
);
283 namespaces
|= CLONE_NEWIPC
;
287 open_namespace_fd(CLONE_NEWNET
, optarg
);
289 namespaces
|= CLONE_NEWNET
;
293 open_namespace_fd(CLONE_NEWPID
, optarg
);
295 namespaces
|= CLONE_NEWPID
;
299 open_namespace_fd(CLONE_NEWCGROUP
, optarg
);
301 namespaces
|= CLONE_NEWCGROUP
;
305 open_namespace_fd(CLONE_NEWUSER
, optarg
);
307 namespaces
|= CLONE_NEWUSER
;
310 uid
= strtoul_or_err(optarg
, _("failed to parse uid"));
314 gid
= strtoul_or_err(optarg
, _("failed to parse gid"));
322 open_target_fd(&root_fd
, "root", optarg
);
328 open_target_fd(&wd_fd
, "cwd", optarg
);
332 case OPT_PRESERVE_CRED
:
335 #ifdef HAVE_LIBSELINUX
341 errtryhelp(EXIT_FAILURE
);
345 #ifdef HAVE_LIBSELINUX
346 if (selinux
&& is_selinux_enabled() > 0) {
349 if (!namespace_target_pid
)
350 errx(EXIT_FAILURE
, _("no target PID specified for --follow-context"));
351 if (getpidcon(namespace_target_pid
, &scon
) < 0)
352 errx(EXIT_FAILURE
, _("failed to get %d SELinux context"),
353 (int) namespace_target_pid
);
354 if (setexeccon(scon
) < 0)
355 errx(EXIT_FAILURE
, _("failed to set exec context to '%s'"), scon
);
361 if (!namespace_target_pid
)
362 errx(EXIT_FAILURE
, _("no target PID specified for --all"));
363 for (nsfile
= namespace_files
; nsfile
->nstype
; nsfile
++) {
365 continue; /* namespace already specified */
367 /* It is not permitted to use setns(2) to reenter the caller's
368 * current user namespace; see setns(2) man page for more details.
370 if (nsfile
->nstype
& CLONE_NEWUSER
371 && is_same_namespace(getpid(), namespace_target_pid
, nsfile
->name
))
374 namespaces
|= nsfile
->nstype
;
379 * Open remaining namespace and directory descriptors.
381 for (nsfile
= namespace_files
; nsfile
->nstype
; nsfile
++)
382 if (nsfile
->nstype
& namespaces
)
383 open_namespace_fd(nsfile
->nstype
, NULL
);
385 open_target_fd(&root_fd
, "root", NULL
);
387 open_target_fd(&wd_fd
, "cwd", NULL
);
390 * Update namespaces variable to contain all requested namespaces
392 for (nsfile
= namespace_files
; nsfile
->nstype
; nsfile
++) {
395 namespaces
|= nsfile
->nstype
;
398 /* for user namespaces we always set UID and GID (default is 0)
399 * and clear root's groups if --preserve-credentials is no specified */
400 if ((namespaces
& CLONE_NEWUSER
) && !preserve_cred
) {
401 force_uid
= true, force_gid
= true;
403 /* We call setgroups() before and after we enter user namespace,
404 * let's complain only if both fail */
405 if (setgroups(0, NULL
) != 0)
410 * Now that we know which namespaces we want to enter, enter
411 * them. Do this in two passes, not entering the user
412 * namespace on the first pass. So if we're deprivileging the
413 * container we'll enter the user namespace last and if we're
414 * privileging it then we enter the user namespace first
415 * (because the initial setns will fail).
417 for (pass
= 0; pass
< 2; pass
++) {
418 for (nsfile
= namespace_files
+ 1 - pass
; nsfile
->nstype
; nsfile
++) {
421 if (nsfile
->nstype
== CLONE_NEWPID
&& do_fork
== -1)
423 if (setns(nsfile
->fd
, nsfile
->nstype
)) {
426 _("reassociate to namespace '%s' failed"),
437 /* Remember the current working directory if I'm not changing it */
438 if (root_fd
>= 0 && wd_fd
< 0) {
439 wd_fd
= open(".", O_RDONLY
);
442 _("cannot open current working directory"));
445 /* Change the root directory */
447 if (fchdir(root_fd
) < 0)
449 _("change directory by root file descriptor failed"));
452 err(EXIT_FAILURE
, _("chroot failed"));
458 /* Change the working directory */
460 if (fchdir(wd_fd
) < 0)
462 _("change directory by working directory file descriptor failed"));
471 if (force_uid
|| force_gid
) {
472 if (force_gid
&& setgroups(0, NULL
) != 0 && setgroups_nerrs
) /* drop supplementary groups */
473 err(EXIT_FAILURE
, _("setgroups failed"));
474 if (force_gid
&& setgid(gid
) < 0) /* change GID */
475 err(EXIT_FAILURE
, _("setgid failed"));
476 if (force_uid
&& setuid(uid
) < 0) /* change UID */
477 err(EXIT_FAILURE
, _("setuid failed"));
481 execvp(argv
[optind
], argv
+ optind
);
482 err(EXIT_FAILURE
, _("failed to execute %s"), argv
[optind
]);