]>
Commit | Line | Data |
---|---|---|
f8aa8e94 | 1 | /* |
9abd5e4b | 2 | * SPDX-License-Identifier: GPL-2.0 |
f8aa8e94 EB |
3 | * |
4 | * This program is free software; you can redistribute it and/or modify it | |
5 | * under the terms of the GNU General Public License as published by the | |
6 | * Free Software Foundation; version 2. | |
7 | * | |
9abd5e4b | 8 | * Copyright (C) 2012-2023 Eric Biederman <ebiederm@xmission.com> |
f8aa8e94 | 9 | * |
9abd5e4b | 10 | * nsenter(1) - command-line interface for setns(2) |
f8aa8e94 | 11 | */ |
f8aa8e94 EB |
12 | #include <dirent.h> |
13 | #include <errno.h> | |
14 | #include <getopt.h> | |
15 | #include <sched.h> | |
16 | #include <stdio.h> | |
17 | #include <stdlib.h> | |
984e1b7c | 18 | #include <stdbool.h> |
f8aa8e94 | 19 | #include <unistd.h> |
dfd8b117 | 20 | #include <assert.h> |
57580694 ZJS |
21 | #include <sys/types.h> |
22 | #include <sys/wait.h> | |
99d7e174 | 23 | #include <grp.h> |
974cc006 | 24 | #include <sys/stat.h> |
b40650b7 | 25 | #include <sys/statfs.h> |
f8aa8e94 | 26 | |
01a6d803 | 27 | #include <sys/ioctl.h> |
28 | #ifdef HAVE_LINUX_NSFS_H | |
29 | # include <linux/nsfs.h> | |
30 | #endif | |
31 | #ifndef NS_GET_USERNS | |
32 | # define NS_GET_USERNS _IO(0xb7, 0x1) | |
33 | #endif | |
34 | ||
355ee3b8 KZ |
35 | #ifdef HAVE_LIBSELINUX |
36 | # include <selinux/selinux.h> | |
37 | #endif | |
38 | ||
0d3ec860 | 39 | #include "strutils.h" |
f8aa8e94 EB |
40 | #include "nls.h" |
41 | #include "c.h" | |
42 | #include "closestream.h" | |
c91280a4 | 43 | #include "namespace.h" |
57580694 | 44 | #include "exec_shell.h" |
0cbb001a | 45 | #include "optutils.h" |
4e9ec856 | 46 | #include "xalloc.h" |
47 | #include "all-io.h" | |
48 | #include "env.h" | |
5b175fa9 | 49 | #include "caputils.h" |
b40650b7 | 50 | #include "statfs_magic.h" |
51 | #include "pathnames.h" | |
f8aa8e94 | 52 | |
a167328a | 53 | static struct namespace_file { |
f8aa8e94 | 54 | int nstype; |
f9bbdea6 | 55 | const char *name; |
f8aa8e94 EB |
56 | int fd; |
57 | } namespace_files[] = { | |
ebbc87cd | 58 | /* Careful the order is significant in this array. |
f8aa8e94 | 59 | * |
854d0fef JB |
60 | * The user namespace comes either first or last: first if |
61 | * you're using it to increase your privilege and last if | |
62 | * you're using it to decrease. We enter the namespaces in | |
63 | * two passes starting initially from offset 1 and then offset | |
64 | * 0 if that fails. | |
f8aa8e94 | 65 | */ |
f9e7b66d SH |
66 | { .nstype = CLONE_NEWUSER, .name = "ns/user", .fd = -1 }, |
67 | { .nstype = CLONE_NEWCGROUP,.name = "ns/cgroup", .fd = -1 }, | |
68 | { .nstype = CLONE_NEWIPC, .name = "ns/ipc", .fd = -1 }, | |
69 | { .nstype = CLONE_NEWUTS, .name = "ns/uts", .fd = -1 }, | |
70 | { .nstype = CLONE_NEWNET, .name = "ns/net", .fd = -1 }, | |
71 | { .nstype = CLONE_NEWPID, .name = "ns/pid", .fd = -1 }, | |
72 | { .nstype = CLONE_NEWNS, .name = "ns/mnt", .fd = -1 }, | |
7f1f0584 | 73 | { .nstype = CLONE_NEWTIME, .name = "ns/time", .fd = -1 }, |
9905912f | 74 | { .nstype = 0, .name = NULL, .fd = -1 } |
f8aa8e94 EB |
75 | }; |
76 | ||
fa2cd89a | 77 | static void __attribute__((__noreturn__)) usage(void) |
f8aa8e94 | 78 | { |
fa2cd89a | 79 | FILE *out = stdout; |
f8aa8e94 EB |
80 | |
81 | fputs(USAGE_HEADER, out); | |
0f0b5823 | 82 | fprintf(out, _(" %s [options] [<program> [<argument>...]]\n"), |
f8aa8e94 EB |
83 | program_invocation_short_name); |
84 | ||
451dbcfa BS |
85 | fputs(USAGE_SEPARATOR, out); |
86 | fputs(_("Run a program with namespaces of other processes.\n"), out); | |
87 | ||
f8aa8e94 | 88 | fputs(USAGE_OPTIONS, out); |
974cc006 | 89 | fputs(_(" -a, --all enter all namespaces\n"), out); |
26f879ed | 90 | fputs(_(" -t, --target <pid> target process to get namespaces from\n"), out); |
42f00a7d BS |
91 | fputs(_(" -m, --mount[=<file>] enter mount namespace\n"), out); |
92 | fputs(_(" -u, --uts[=<file>] enter UTS namespace (hostname etc)\n"), out); | |
93 | fputs(_(" -i, --ipc[=<file>] enter System V IPC namespace\n"), out); | |
94 | fputs(_(" -n, --net[=<file>] enter network namespace\n"), out); | |
95 | fputs(_(" -p, --pid[=<file>] enter pid namespace\n"), out); | |
f9e7b66d | 96 | fputs(_(" -C, --cgroup[=<file>] enter cgroup namespace\n"), out); |
42f00a7d | 97 | fputs(_(" -U, --user[=<file>] enter user namespace\n"), out); |
01a6d803 | 98 | fputs(_(" --user-parent enter parent user namespace\n"), out); |
7f1f0584 | 99 | fputs(_(" -T, --time[=<file>] enter time namespace\n"), out); |
ab8b10b2 KZ |
100 | fputs(_(" -S, --setuid[=<uid>] set uid in entered namespace\n"), out); |
101 | fputs(_(" -G, --setgid[=<gid>] set gid in entered namespace\n"), out); | |
e99a6626 | 102 | fputs(_(" --preserve-credentials do not touch uids or gids\n"), out); |
5b175fa9 | 103 | fputs(_(" --keep-caps retain capabilities granted in user namespaces\n"), out); |
42f00a7d BS |
104 | fputs(_(" -r, --root[=<dir>] set the root directory\n"), out); |
105 | fputs(_(" -w, --wd[=<dir>] set the working directory\n"), out); | |
4e9ec856 | 106 | fputs(_(" -W, --wdns <dir> set the working directory in namespace\n"), out); |
107 | fputs(_(" -e, --env inherit environment variables from target process\n"), out); | |
26f879ed | 108 | fputs(_(" -F, --no-fork do not fork before exec'ing <program>\n"), out); |
b40650b7 | 109 | fputs(_(" -c, --join-cgroup join the cgroup of the target process\n"), out); |
355ee3b8 KZ |
110 | #ifdef HAVE_LIBSELINUX |
111 | fputs(_(" -Z, --follow-context set SELinux context according to --target PID\n"), out); | |
112 | #endif | |
26f879ed | 113 | |
f8aa8e94 | 114 | fputs(USAGE_SEPARATOR, out); |
bad4c729 MY |
115 | fprintf(out, USAGE_HELP_OPTIONS(24)); |
116 | fprintf(out, USAGE_MAN_TAIL("nsenter(1)")); | |
f8aa8e94 | 117 | |
fa2cd89a | 118 | exit(EXIT_SUCCESS); |
f8aa8e94 EB |
119 | } |
120 | ||
121 | static pid_t namespace_target_pid = 0; | |
122 | static int root_fd = -1; | |
123 | static int wd_fd = -1; | |
4e9ec856 | 124 | static int env_fd = -1; |
597ae346 | 125 | static int uid_gid_fd = -1; |
b40650b7 | 126 | static int cgroup_procs_fd = -1; |
f8aa8e94 | 127 | |
01a6d803 | 128 | static void set_parent_user_ns_fd(void) |
129 | { | |
130 | struct namespace_file *nsfile = NULL; | |
131 | struct namespace_file *user_nsfile = NULL; | |
132 | int parent_ns = -1; | |
133 | ||
134 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) { | |
135 | if (nsfile->nstype == CLONE_NEWUSER) | |
136 | user_nsfile = nsfile; | |
137 | ||
138 | if (nsfile->fd == -1) | |
139 | continue; | |
140 | ||
141 | parent_ns = ioctl(nsfile->fd, NS_GET_USERNS); | |
142 | if (parent_ns < 0) | |
143 | err(EXIT_FAILURE, _("failed to open parent ns of %s"), nsfile->name); | |
144 | ||
145 | break; | |
146 | } | |
147 | ||
148 | if (parent_ns < 0) | |
149 | errx(EXIT_FAILURE, _("no namespaces to get parent of")); | |
32c78705 KZ |
150 | if (user_nsfile) |
151 | user_nsfile->fd = parent_ns; | |
01a6d803 | 152 | } |
153 | ||
154 | ||
f9bbdea6 | 155 | static void open_target_fd(int *fd, const char *type, const char *path) |
f8aa8e94 EB |
156 | { |
157 | char pathbuf[PATH_MAX]; | |
158 | ||
159 | if (!path && namespace_target_pid) { | |
160 | snprintf(pathbuf, sizeof(pathbuf), "/proc/%u/%s", | |
a167328a | 161 | namespace_target_pid, type); |
f8aa8e94 EB |
162 | path = pathbuf; |
163 | } | |
164 | if (!path) | |
a167328a SK |
165 | errx(EXIT_FAILURE, |
166 | _("neither filename nor target pid supplied for %s"), | |
167 | type); | |
f8aa8e94 EB |
168 | |
169 | if (*fd >= 0) | |
170 | close(*fd); | |
171 | ||
172 | *fd = open(path, O_RDONLY); | |
173 | if (*fd < 0) | |
8b7a7750 | 174 | err(EXIT_FAILURE, _("cannot open %s"), path); |
f8aa8e94 EB |
175 | } |
176 | ||
f9bbdea6 | 177 | static void open_namespace_fd(int nstype, const char *path) |
f8aa8e94 EB |
178 | { |
179 | struct namespace_file *nsfile; | |
180 | ||
181 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) { | |
182 | if (nstype != nsfile->nstype) | |
183 | continue; | |
184 | ||
185 | open_target_fd(&nsfile->fd, nsfile->name, path); | |
186 | return; | |
187 | } | |
188 | /* This should never happen */ | |
dfd8b117 | 189 | assert(nsfile->nstype); |
f8aa8e94 EB |
190 | } |
191 | ||
974cc006 KZ |
192 | static int get_ns_ino(const char *path, ino_t *ino) |
193 | { | |
194 | struct stat st; | |
195 | ||
196 | if (stat(path, &st) != 0) | |
197 | return -errno; | |
198 | *ino = st.st_ino; | |
199 | return 0; | |
200 | } | |
201 | ||
b40650b7 | 202 | static void open_cgroup_procs(void) |
203 | { | |
7ceb5406 KZ |
204 | char *buf = NULL, *path = NULL, *p; |
205 | int cgroup_fd = 0; | |
b40650b7 | 206 | char fdpath[PATH_MAX]; |
207 | ||
208 | open_target_fd(&cgroup_fd, "cgroup", optarg); | |
209 | ||
210 | if (read_all_alloc(cgroup_fd, &buf) < 1) | |
211 | err(EXIT_FAILURE, _("failed to get cgroup path")); | |
212 | ||
7ceb5406 KZ |
213 | p = strtok(buf, "\n"); |
214 | if (p) | |
215 | path = strrchr(p, ':'); | |
b40650b7 | 216 | if (!path) |
217 | err(EXIT_FAILURE, _("failed to get cgroup path")); | |
218 | path++; | |
219 | ||
220 | snprintf(fdpath, sizeof(fdpath), _PATH_SYS_CGROUP "/%s/cgroup.procs", path); | |
c67a2b84 | 221 | |
b40650b7 | 222 | if ((cgroup_procs_fd = open(fdpath, O_WRONLY | O_APPEND)) < 0) |
223 | err(EXIT_FAILURE, _("failed to open cgroup.procs")); | |
c67a2b84 KZ |
224 | |
225 | free(buf); | |
b40650b7 | 226 | } |
227 | ||
228 | static int is_cgroup2(void) | |
229 | { | |
230 | struct statfs fs_stat; | |
231 | int rc; | |
232 | ||
233 | rc = statfs(_PATH_SYS_CGROUP, &fs_stat); | |
234 | if (rc) | |
235 | err(EXIT_FAILURE, _("statfs %s failed"), _PATH_SYS_CGROUP); | |
236 | return F_TYPE_EQUAL(fs_stat.f_type, STATFS_CGROUP2_MAGIC); | |
237 | } | |
238 | ||
239 | static void join_into_cgroup(void) | |
240 | { | |
241 | pid_t pid; | |
242 | char buf[ sizeof(stringify_value(UINT32_MAX)) ]; | |
243 | int len; | |
244 | ||
245 | pid = getpid(); | |
246 | len = snprintf(buf, sizeof(buf), "%zu", (size_t) pid); | |
247 | if (write_all(cgroup_procs_fd, buf, len)) | |
248 | err(EXIT_FAILURE, _("write cgroup.procs failed")); | |
249 | } | |
250 | ||
2f628e8f | 251 | static int is_usable_namespace(pid_t target, const struct namespace_file *nsfile) |
974cc006 KZ |
252 | { |
253 | char path[PATH_MAX]; | |
2f628e8f YG |
254 | ino_t my_ino = 0; |
255 | int rc; | |
974cc006 | 256 | |
2f628e8f YG |
257 | /* Check NS accessibility */ |
258 | snprintf(path, sizeof(path), "/proc/%u/%s", getpid(), nsfile->name); | |
259 | rc = get_ns_ino(path, &my_ino); | |
260 | if (rc == -ENOENT) | |
261 | return false; /* Unsupported NS */ | |
974cc006 | 262 | |
2f628e8f YG |
263 | /* It is not permitted to use setns(2) to reenter the caller's |
264 | * current user namespace; see setns(2) man page for more details. | |
265 | */ | |
266 | if (nsfile->nstype & CLONE_NEWUSER) { | |
267 | ino_t target_ino = 0; | |
268 | ||
269 | snprintf(path, sizeof(path), "/proc/%u/%s", target, nsfile->name); | |
270 | if (get_ns_ino(path, &target_ino) != 0) | |
271 | err(EXIT_FAILURE, _("stat of %s failed"), path); | |
272 | ||
273 | if (my_ino == target_ino) | |
274 | return false; | |
275 | } | |
974cc006 | 276 | |
2f628e8f | 277 | return true; /* All pass */ |
974cc006 KZ |
278 | } |
279 | ||
c9515f86 EB |
280 | static void continue_as_child(void) |
281 | { | |
10d7224f | 282 | pid_t child; |
c9515f86 EB |
283 | int status; |
284 | pid_t ret; | |
285 | ||
10d7224f KZ |
286 | /* Clear any inherited settings */ |
287 | signal(SIGCHLD, SIG_DFL); | |
288 | ||
289 | child = fork(); | |
c9515f86 EB |
290 | if (child < 0) |
291 | err(EXIT_FAILURE, _("fork failed")); | |
292 | ||
293 | /* Only the child returns */ | |
294 | if (child == 0) | |
295 | return; | |
296 | ||
297 | for (;;) { | |
298 | ret = waitpid(child, &status, WUNTRACED); | |
299 | if ((ret == child) && (WIFSTOPPED(status))) { | |
300 | /* The child suspended so suspend us as well */ | |
301 | kill(getpid(), SIGSTOP); | |
302 | kill(child, SIGCONT); | |
303 | } else { | |
304 | break; | |
305 | } | |
306 | } | |
307 | /* Return the child's exit code if possible */ | |
308 | if (WIFEXITED(status)) { | |
309 | exit(WEXITSTATUS(status)); | |
a167328a | 310 | } else if (WIFSIGNALED(status)) { |
c9515f86 EB |
311 | kill(getpid(), WTERMSIG(status)); |
312 | } | |
313 | exit(EXIT_FAILURE); | |
314 | } | |
315 | ||
f8aa8e94 EB |
316 | int main(int argc, char *argv[]) |
317 | { | |
e99a6626 | 318 | enum { |
5b175fa9 DG |
319 | OPT_PRESERVE_CRED = CHAR_MAX + 1, |
320 | OPT_KEEPCAPS, | |
01a6d803 | 321 | OPT_USER_PARENT, |
e99a6626 | 322 | }; |
f8aa8e94 | 323 | static const struct option longopts[] = { |
974cc006 | 324 | { "all", no_argument, NULL, 'a' }, |
f8aa8e94 EB |
325 | { "help", no_argument, NULL, 'h' }, |
326 | { "version", no_argument, NULL, 'V'}, | |
327 | { "target", required_argument, NULL, 't' }, | |
328 | { "mount", optional_argument, NULL, 'm' }, | |
329 | { "uts", optional_argument, NULL, 'u' }, | |
330 | { "ipc", optional_argument, NULL, 'i' }, | |
331 | { "net", optional_argument, NULL, 'n' }, | |
332 | { "pid", optional_argument, NULL, 'p' }, | |
333 | { "user", optional_argument, NULL, 'U' }, | |
f9e7b66d | 334 | { "cgroup", optional_argument, NULL, 'C' }, |
7f1f0584 | 335 | { "time", optional_argument, NULL, 'T' }, |
97bb98ef TW |
336 | { "setuid", required_argument, NULL, 'S' }, |
337 | { "setgid", required_argument, NULL, 'G' }, | |
f8aa8e94 EB |
338 | { "root", optional_argument, NULL, 'r' }, |
339 | { "wd", optional_argument, NULL, 'w' }, | |
0cbb001a | 340 | { "wdns", optional_argument, NULL, 'W' }, |
4e9ec856 | 341 | { "env", no_argument, NULL, 'e' }, |
28384adc | 342 | { "no-fork", no_argument, NULL, 'F' }, |
b40650b7 | 343 | { "join-cgroup", no_argument, NULL, 'c'}, |
e99a6626 | 344 | { "preserve-credentials", no_argument, NULL, OPT_PRESERVE_CRED }, |
5b175fa9 | 345 | { "keep-caps", no_argument, NULL, OPT_KEEPCAPS }, |
01a6d803 | 346 | { "user-parent", no_argument, NULL, OPT_USER_PARENT}, |
355ee3b8 KZ |
347 | #ifdef HAVE_LIBSELINUX |
348 | { "follow-context", no_argument, NULL, 'Z' }, | |
349 | #endif | |
f8aa8e94 EB |
350 | { NULL, 0, NULL, 0 } |
351 | }; | |
0cbb001a KZ |
352 | static const ul_excl_t excl[] = { /* rows and cols in ASCII order */ |
353 | { 'W', 'w' }, | |
354 | { 0 } | |
355 | }; | |
356 | int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT; | |
f8aa8e94 EB |
357 | |
358 | struct namespace_file *nsfile; | |
854d0fef | 359 | int c, pass, namespaces = 0, setgroups_nerrs = 0, preserve_cred = 0; |
ab8b10b2 | 360 | bool do_rd = false, do_wd = false, do_uid = false, force_uid = false, |
01a6d803 | 361 | do_gid = false, force_gid = false, do_env = false, do_all = false, |
b40650b7 | 362 | do_join_cgroup = false, do_user_parent = false; |
57dbcf94 | 363 | int do_fork = -1; /* unknown yet */ |
0cbb001a | 364 | char *wdns = NULL; |
6b9e5bf6 RW |
365 | uid_t uid = 0; |
366 | gid_t gid = 0; | |
5b175fa9 | 367 | int keepcaps = 0; |
4e9ec856 | 368 | struct ul_env_list *envls; |
355ee3b8 KZ |
369 | #ifdef HAVE_LIBSELINUX |
370 | bool selinux = 0; | |
371 | #endif | |
f8aa8e94 | 372 | |
999ac5e2 | 373 | setlocale(LC_ALL, ""); |
f8aa8e94 EB |
374 | bindtextdomain(PACKAGE, LOCALEDIR); |
375 | textdomain(PACKAGE); | |
2c308875 | 376 | close_stdout_atexit(); |
f8aa8e94 | 377 | |
28384adc | 378 | while ((c = |
b40650b7 | 379 | getopt_long(argc, argv, "+ahVt:m::u::i::n::p::C::U::T::S:G:r::w::W::ecFZ", |
28384adc | 380 | longopts, NULL)) != -1) { |
0cbb001a KZ |
381 | |
382 | err_exclusive_options(c, longopts, excl, excl_st); | |
383 | ||
28384adc | 384 | switch (c) { |
974cc006 KZ |
385 | case 'a': |
386 | do_all = true; | |
387 | break; | |
f8aa8e94 | 388 | case 't': |
a167328a SK |
389 | namespace_target_pid = |
390 | strtoul_or_err(optarg, _("failed to parse pid")); | |
f8aa8e94 EB |
391 | break; |
392 | case 'm': | |
984e1b7c ZJS |
393 | if (optarg) |
394 | open_namespace_fd(CLONE_NEWNS, optarg); | |
395 | else | |
396 | namespaces |= CLONE_NEWNS; | |
f8aa8e94 EB |
397 | break; |
398 | case 'u': | |
984e1b7c ZJS |
399 | if (optarg) |
400 | open_namespace_fd(CLONE_NEWUTS, optarg); | |
401 | else | |
402 | namespaces |= CLONE_NEWUTS; | |
f8aa8e94 EB |
403 | break; |
404 | case 'i': | |
984e1b7c ZJS |
405 | if (optarg) |
406 | open_namespace_fd(CLONE_NEWIPC, optarg); | |
407 | else | |
408 | namespaces |= CLONE_NEWIPC; | |
f8aa8e94 EB |
409 | break; |
410 | case 'n': | |
984e1b7c ZJS |
411 | if (optarg) |
412 | open_namespace_fd(CLONE_NEWNET, optarg); | |
413 | else | |
414 | namespaces |= CLONE_NEWNET; | |
f8aa8e94 EB |
415 | break; |
416 | case 'p': | |
984e1b7c ZJS |
417 | if (optarg) |
418 | open_namespace_fd(CLONE_NEWPID, optarg); | |
419 | else | |
420 | namespaces |= CLONE_NEWPID; | |
f8aa8e94 | 421 | break; |
f9e7b66d SH |
422 | case 'C': |
423 | if (optarg) | |
424 | open_namespace_fd(CLONE_NEWCGROUP, optarg); | |
425 | else | |
426 | namespaces |= CLONE_NEWCGROUP; | |
427 | break; | |
f8aa8e94 | 428 | case 'U': |
984e1b7c ZJS |
429 | if (optarg) |
430 | open_namespace_fd(CLONE_NEWUSER, optarg); | |
431 | else | |
432 | namespaces |= CLONE_NEWUSER; | |
f8aa8e94 | 433 | break; |
7f1f0584 AR |
434 | case 'T': |
435 | if (optarg) | |
436 | open_namespace_fd(CLONE_NEWTIME, optarg); | |
437 | else | |
438 | namespaces |= CLONE_NEWTIME; | |
439 | break; | |
6b9e5bf6 | 440 | case 'S': |
97bb98ef | 441 | if (strcmp(optarg, "follow") == 0) |
597ae346 | 442 | do_uid = true; |
97bb98ef TW |
443 | else |
444 | uid = strtoul_or_err(optarg, _("failed to parse uid")); | |
47f42c1d | 445 | force_uid = true; |
6b9e5bf6 RW |
446 | break; |
447 | case 'G': | |
97bb98ef | 448 | if (strcmp(optarg, "follow") == 0) |
597ae346 | 449 | do_gid = true; |
97bb98ef TW |
450 | else |
451 | gid = strtoul_or_err(optarg, _("failed to parse gid")); | |
47f42c1d | 452 | force_gid = true; |
6b9e5bf6 | 453 | break; |
28384adc | 454 | case 'F': |
57dbcf94 | 455 | do_fork = 0; |
f8aa8e94 | 456 | break; |
b40650b7 | 457 | case 'c': |
458 | do_join_cgroup = true; | |
459 | break; | |
f8aa8e94 | 460 | case 'r': |
984e1b7c | 461 | if (optarg) |
82524a13 | 462 | open_target_fd(&root_fd, "root", optarg); |
984e1b7c ZJS |
463 | else |
464 | do_rd = true; | |
f8aa8e94 EB |
465 | break; |
466 | case 'w': | |
984e1b7c | 467 | if (optarg) |
82524a13 | 468 | open_target_fd(&wd_fd, "cwd", optarg); |
984e1b7c ZJS |
469 | else |
470 | do_wd = true; | |
f8aa8e94 | 471 | break; |
0cbb001a KZ |
472 | case 'W': |
473 | wdns = optarg; | |
474 | break; | |
4e9ec856 | 475 | case 'e': |
476 | do_env = true; | |
477 | break; | |
e99a6626 KZ |
478 | case OPT_PRESERVE_CRED: |
479 | preserve_cred = 1; | |
480 | break; | |
5b175fa9 DG |
481 | case OPT_KEEPCAPS: |
482 | keepcaps = 1; | |
483 | break; | |
01a6d803 | 484 | case OPT_USER_PARENT: |
485 | do_user_parent = true; | |
486 | break; | |
355ee3b8 KZ |
487 | #ifdef HAVE_LIBSELINUX |
488 | case 'Z': | |
489 | selinux = 1; | |
490 | break; | |
491 | #endif | |
2c308875 KZ |
492 | case 'h': |
493 | usage(); | |
494 | case 'V': | |
495 | print_version(EXIT_SUCCESS); | |
f8aa8e94 | 496 | default: |
677ec86c | 497 | errtryhelp(EXIT_FAILURE); |
f8aa8e94 EB |
498 | } |
499 | } | |
500 | ||
355ee3b8 KZ |
501 | #ifdef HAVE_LIBSELINUX |
502 | if (selinux && is_selinux_enabled() > 0) { | |
503 | char *scon = NULL; | |
504 | ||
505 | if (!namespace_target_pid) | |
506 | errx(EXIT_FAILURE, _("no target PID specified for --follow-context")); | |
507 | if (getpidcon(namespace_target_pid, &scon) < 0) | |
508 | errx(EXIT_FAILURE, _("failed to get %d SELinux context"), | |
509 | (int) namespace_target_pid); | |
510 | if (setexeccon(scon) < 0) | |
511 | errx(EXIT_FAILURE, _("failed to set exec context to '%s'"), scon); | |
512 | freecon(scon); | |
513 | } | |
514 | #endif | |
974cc006 KZ |
515 | |
516 | if (do_all) { | |
517 | if (!namespace_target_pid) | |
518 | errx(EXIT_FAILURE, _("no target PID specified for --all")); | |
519 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) { | |
520 | if (nsfile->fd >= 0) | |
521 | continue; /* namespace already specified */ | |
522 | ||
2f628e8f | 523 | if (!is_usable_namespace(namespace_target_pid, nsfile)) |
974cc006 KZ |
524 | continue; |
525 | ||
526 | namespaces |= nsfile->nstype; | |
527 | } | |
528 | } | |
529 | ||
984e1b7c ZJS |
530 | /* |
531 | * Open remaining namespace and directory descriptors. | |
532 | */ | |
533 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) | |
534 | if (nsfile->nstype & namespaces) | |
535 | open_namespace_fd(nsfile->nstype, NULL); | |
536 | if (do_rd) | |
537 | open_target_fd(&root_fd, "root", NULL); | |
538 | if (do_wd) | |
539 | open_target_fd(&wd_fd, "cwd", NULL); | |
4e9ec856 | 540 | if (do_env) |
541 | open_target_fd(&env_fd, "environ", NULL); | |
597ae346 TW |
542 | if (do_uid || do_gid) |
543 | open_target_fd(&uid_gid_fd, "", NULL); | |
b40650b7 | 544 | if (do_join_cgroup) { |
545 | if (!is_cgroup2()) | |
546 | errx(EXIT_FAILURE, _("--join-cgroup is only supported in cgroup v2")); | |
547 | open_cgroup_procs(); | |
548 | } | |
984e1b7c | 549 | |
01a6d803 | 550 | /* |
551 | * Get parent userns from any available ns. | |
552 | */ | |
553 | if (do_user_parent) | |
554 | set_parent_user_ns_fd(); | |
555 | ||
83fb8aa5 KZ |
556 | /* |
557 | * Update namespaces variable to contain all requested namespaces | |
558 | */ | |
559 | for (nsfile = namespace_files; nsfile->nstype; nsfile++) { | |
560 | if (nsfile->fd < 0) | |
561 | continue; | |
562 | namespaces |= nsfile->nstype; | |
563 | } | |
564 | ||
e99a6626 KZ |
565 | /* for user namespaces we always set UID and GID (default is 0) |
566 | * and clear root's groups if --preserve-credentials is no specified */ | |
567 | if ((namespaces & CLONE_NEWUSER) && !preserve_cred) { | |
568 | force_uid = true, force_gid = true; | |
569 | ||
570 | /* We call setgroups() before and after we enter user namespace, | |
571 | * let's complain only if both fail */ | |
572 | if (setgroups(0, NULL) != 0) | |
573 | setgroups_nerrs++; | |
574 | } | |
575 | ||
f8aa8e94 | 576 | /* |
854d0fef JB |
577 | * Now that we know which namespaces we want to enter, enter |
578 | * them. Do this in two passes, not entering the user | |
579 | * namespace on the first pass. So if we're deprivileging the | |
580 | * container we'll enter the user namespace last and if we're | |
9e930041 | 581 | * privileging it then we enter the user namespace first |
854d0fef | 582 | * (because the initial setns will fail). |
f8aa8e94 | 583 | */ |
854d0fef JB |
584 | for (pass = 0; pass < 2; pass ++) { |
585 | for (nsfile = namespace_files + 1 - pass; nsfile->nstype; nsfile++) { | |
586 | if (nsfile->fd < 0) | |
587 | continue; | |
588 | if (nsfile->nstype == CLONE_NEWPID && do_fork == -1) | |
589 | do_fork = 1; | |
590 | if (setns(nsfile->fd, nsfile->nstype)) { | |
591 | if (pass != 0) | |
592 | err(EXIT_FAILURE, | |
593 | _("reassociate to namespace '%s' failed"), | |
594 | nsfile->name); | |
595 | else | |
596 | continue; | |
597 | } | |
598 | ||
599 | close(nsfile->fd); | |
600 | nsfile->fd = -1; | |
601 | } | |
f8aa8e94 EB |
602 | } |
603 | ||
604 | /* Remember the current working directory if I'm not changing it */ | |
0cbb001a | 605 | if (root_fd >= 0 && wd_fd < 0 && wdns == NULL) { |
f8aa8e94 EB |
606 | wd_fd = open(".", O_RDONLY); |
607 | if (wd_fd < 0) | |
a167328a SK |
608 | err(EXIT_FAILURE, |
609 | _("cannot open current working directory")); | |
f8aa8e94 EB |
610 | } |
611 | ||
612 | /* Change the root directory */ | |
613 | if (root_fd >= 0) { | |
614 | if (fchdir(root_fd) < 0) | |
a167328a SK |
615 | err(EXIT_FAILURE, |
616 | _("change directory by root file descriptor failed")); | |
f8aa8e94 EB |
617 | |
618 | if (chroot(".") < 0) | |
619 | err(EXIT_FAILURE, _("chroot failed")); | |
12558a4c SK |
620 | if (chdir("/")) |
621 | err(EXIT_FAILURE, _("cannot change directory to %s"), "/"); | |
f8aa8e94 EB |
622 | |
623 | close(root_fd); | |
624 | root_fd = -1; | |
625 | } | |
626 | ||
0cbb001a KZ |
627 | /* working directory specified as in-namespace path */ |
628 | if (wdns) { | |
629 | wd_fd = open(wdns, O_RDONLY); | |
630 | if (wd_fd < 0) | |
631 | err(EXIT_FAILURE, | |
632 | _("cannot open current working directory")); | |
633 | } | |
634 | ||
f8aa8e94 EB |
635 | /* Change the working directory */ |
636 | if (wd_fd >= 0) { | |
637 | if (fchdir(wd_fd) < 0) | |
a167328a SK |
638 | err(EXIT_FAILURE, |
639 | _("change directory by working directory file descriptor failed")); | |
f8aa8e94 EB |
640 | |
641 | close(wd_fd); | |
642 | wd_fd = -1; | |
643 | } | |
644 | ||
4e9ec856 | 645 | /* Pass environment variables of the target process to the spawned process */ |
646 | if (env_fd >= 0) { | |
647 | if ((envls = env_from_fd(env_fd)) == NULL) | |
648 | err(EXIT_FAILURE, _("failed to get environment variables")); | |
649 | clearenv(); | |
650 | if (env_list_setenv(envls) < 0) | |
651 | err(EXIT_FAILURE, _("failed to set environment variables")); | |
652 | env_list_free(envls); | |
653 | close(env_fd); | |
654 | } | |
655 | ||
b40650b7 | 656 | // Join into the target cgroup |
657 | if (cgroup_procs_fd >= 0) | |
658 | join_into_cgroup(); | |
659 | ||
597ae346 TW |
660 | if (uid_gid_fd >= 0) { |
661 | struct stat st; | |
662 | ||
663 | if (fstat(uid_gid_fd, &st) > 0) | |
664 | err(EXIT_FAILURE, _("can not get process stat")); | |
665 | ||
666 | close(uid_gid_fd); | |
667 | uid_gid_fd = -1; | |
668 | ||
669 | if (do_uid) | |
670 | uid = st.st_uid; | |
671 | if (do_gid) | |
672 | gid = st.st_gid; | |
673 | } | |
674 | ||
57dbcf94 | 675 | if (do_fork == 1) |
c9515f86 | 676 | continue_as_child(); |
f8aa8e94 | 677 | |
47f42c1d | 678 | if (force_uid || force_gid) { |
e99a6626 | 679 | if (force_gid && setgroups(0, NULL) != 0 && setgroups_nerrs) /* drop supplementary groups */ |
99d7e174 | 680 | err(EXIT_FAILURE, _("setgroups failed")); |
47f42c1d | 681 | if (force_gid && setgid(gid) < 0) /* change GID */ |
6b9e5bf6 | 682 | err(EXIT_FAILURE, _("setgid failed")); |
47f42c1d | 683 | if (force_uid && setuid(uid) < 0) /* change UID */ |
99d7e174 | 684 | err(EXIT_FAILURE, _("setuid failed")); |
6b9e5bf6 RW |
685 | } |
686 | ||
5b175fa9 DG |
687 | if (keepcaps && (namespaces & CLONE_NEWUSER)) |
688 | cap_permitted_to_ambient(); | |
689 | ||
57580694 ZJS |
690 | if (optind < argc) { |
691 | execvp(argv[optind], argv + optind); | |
fd777151 | 692 | errexec(argv[optind]); |
57580694 ZJS |
693 | } |
694 | exec_shell(); | |
f8aa8e94 | 695 | } |