]> git.ipfire.org Git - thirdparty/util-linux.git/blob - sys-utils/nsenter.c
Merge branch 'uuid-time64_t' of https://github.com/thkukuk/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 #include <sys/statfs.h>
34
35 #include <sys/ioctl.h>
36 #ifdef HAVE_LINUX_NSFS_H
37 # include <linux/nsfs.h>
38 #endif
39 #ifndef NS_GET_USERNS
40 # define NS_GET_USERNS _IO(0xb7, 0x1)
41 #endif
42
43 #ifdef HAVE_LIBSELINUX
44 # include <selinux/selinux.h>
45 #endif
46
47 #include "strutils.h"
48 #include "nls.h"
49 #include "c.h"
50 #include "closestream.h"
51 #include "namespace.h"
52 #include "exec_shell.h"
53 #include "optutils.h"
54 #include "xalloc.h"
55 #include "all-io.h"
56 #include "env.h"
57 #include "caputils.h"
58 #include "statfs_magic.h"
59 #include "pathnames.h"
60
61 static struct namespace_file {
62 int nstype;
63 const char *name;
64 int fd;
65 } namespace_files[] = {
66 /* Careful the order is significant in this array.
67 *
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
72 * 0 if that fails.
73 */
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 }
83 };
84
85 static void __attribute__((__noreturn__)) usage(void)
86 {
87 FILE *out = stdout;
88
89 fputs(USAGE_HEADER, out);
90 fprintf(out, _(" %s [options] [<program> [<argument>...]]\n"),
91 program_invocation_short_name);
92
93 fputs(USAGE_SEPARATOR, out);
94 fputs(_("Run a program with namespaces of other processes.\n"), out);
95
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);
120 #endif
121
122 fputs(USAGE_SEPARATOR, out);
123 fprintf(out, USAGE_HELP_OPTIONS(24));
124 fprintf(out, USAGE_MAN_TAIL("nsenter(1)"));
125
126 exit(EXIT_SUCCESS);
127 }
128
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;
135
136 static void set_parent_user_ns_fd(void)
137 {
138 struct namespace_file *nsfile = NULL;
139 struct namespace_file *user_nsfile = NULL;
140 int parent_ns = -1;
141
142 for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
143 if (nsfile->nstype == CLONE_NEWUSER)
144 user_nsfile = nsfile;
145
146 if (nsfile->fd == -1)
147 continue;
148
149 parent_ns = ioctl(nsfile->fd, NS_GET_USERNS);
150 if (parent_ns < 0)
151 err(EXIT_FAILURE, _("failed to open parent ns of %s"), nsfile->name);
152
153 break;
154 }
155
156 if (parent_ns < 0)
157 errx(EXIT_FAILURE, _("no namespaces to get parent of"));
158 if (user_nsfile)
159 user_nsfile->fd = parent_ns;
160 }
161
162
163 static void open_target_fd(int *fd, const char *type, const char *path)
164 {
165 char pathbuf[PATH_MAX];
166
167 if (!path && namespace_target_pid) {
168 snprintf(pathbuf, sizeof(pathbuf), "/proc/%u/%s",
169 namespace_target_pid, type);
170 path = pathbuf;
171 }
172 if (!path)
173 errx(EXIT_FAILURE,
174 _("neither filename nor target pid supplied for %s"),
175 type);
176
177 if (*fd >= 0)
178 close(*fd);
179
180 *fd = open(path, O_RDONLY);
181 if (*fd < 0)
182 err(EXIT_FAILURE, _("cannot open %s"), path);
183 }
184
185 static void open_namespace_fd(int nstype, const char *path)
186 {
187 struct namespace_file *nsfile;
188
189 for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
190 if (nstype != nsfile->nstype)
191 continue;
192
193 open_target_fd(&nsfile->fd, nsfile->name, path);
194 return;
195 }
196 /* This should never happen */
197 assert(nsfile->nstype);
198 }
199
200 static int get_ns_ino(const char *path, ino_t *ino)
201 {
202 struct stat st;
203
204 if (stat(path, &st) != 0)
205 return -errno;
206 *ino = st.st_ino;
207 return 0;
208 }
209
210 static void open_cgroup_procs(void)
211 {
212 char *buf = NULL, *path = NULL, *p;
213 int cgroup_fd = 0;
214 char fdpath[PATH_MAX];
215
216 open_target_fd(&cgroup_fd, "cgroup", optarg);
217
218 if (read_all_alloc(cgroup_fd, &buf) < 1)
219 err(EXIT_FAILURE, _("failed to get cgroup path"));
220
221 p = strtok(buf, "\n");
222 if (p)
223 path = strrchr(p, ':');
224 if (!path)
225 err(EXIT_FAILURE, _("failed to get cgroup path"));
226 path++;
227
228 snprintf(fdpath, sizeof(fdpath), _PATH_SYS_CGROUP "/%s/cgroup.procs", path);
229
230 if ((cgroup_procs_fd = open(fdpath, O_WRONLY | O_APPEND)) < 0)
231 err(EXIT_FAILURE, _("failed to open cgroup.procs"));
232
233 free(buf);
234 }
235
236 static int is_cgroup2(void)
237 {
238 struct statfs fs_stat;
239 int rc;
240
241 rc = statfs(_PATH_SYS_CGROUP, &fs_stat);
242 if (rc)
243 err(EXIT_FAILURE, _("statfs %s failed"), _PATH_SYS_CGROUP);
244 return F_TYPE_EQUAL(fs_stat.f_type, STATFS_CGROUP2_MAGIC);
245 }
246
247 static void join_into_cgroup(void)
248 {
249 pid_t pid;
250 char buf[ sizeof(stringify_value(UINT32_MAX)) ];
251 int len;
252
253 pid = getpid();
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"));
257 }
258
259 static int is_usable_namespace(pid_t target, const struct namespace_file *nsfile)
260 {
261 char path[PATH_MAX];
262 ino_t my_ino = 0;
263 int rc;
264
265 /* Check NS accessibility */
266 snprintf(path, sizeof(path), "/proc/%u/%s", getpid(), nsfile->name);
267 rc = get_ns_ino(path, &my_ino);
268 if (rc == -ENOENT)
269 return false; /* Unsupported NS */
270
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.
273 */
274 if (nsfile->nstype & CLONE_NEWUSER) {
275 ino_t target_ino = 0;
276
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);
280
281 if (my_ino == target_ino)
282 return false;
283 }
284
285 return true; /* All pass */
286 }
287
288 static void continue_as_child(void)
289 {
290 pid_t child;
291 int status;
292 pid_t ret;
293
294 /* Clear any inherited settings */
295 signal(SIGCHLD, SIG_DFL);
296
297 child = fork();
298 if (child < 0)
299 err(EXIT_FAILURE, _("fork failed"));
300
301 /* Only the child returns */
302 if (child == 0)
303 return;
304
305 for (;;) {
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);
311 } else {
312 break;
313 }
314 }
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));
320 }
321 exit(EXIT_FAILURE);
322 }
323
324 int main(int argc, char *argv[])
325 {
326 enum {
327 OPT_PRESERVE_CRED = CHAR_MAX + 1,
328 OPT_KEEPCAPS,
329 OPT_USER_PARENT,
330 };
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' },
357 #endif
358 { NULL, 0, NULL, 0 }
359 };
360 static const ul_excl_t excl[] = { /* rows and cols in ASCII order */
361 { 'W', 'w' },
362 { 0 }
363 };
364 int excl_st[ARRAY_SIZE(excl)] = UL_EXCL_STATUS_INIT;
365
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 */
372 char *wdns = NULL;
373 uid_t uid = 0;
374 gid_t gid = 0;
375 int keepcaps = 0;
376 struct ul_env_list *envls;
377 #ifdef HAVE_LIBSELINUX
378 bool selinux = 0;
379 #endif
380
381 setlocale(LC_ALL, "");
382 bindtextdomain(PACKAGE, LOCALEDIR);
383 textdomain(PACKAGE);
384 close_stdout_atexit();
385
386 while ((c =
387 getopt_long(argc, argv, "+ahVt:m::u::i::n::p::C::U::T::S:G:r::w::W::ecFZ",
388 longopts, NULL)) != -1) {
389
390 err_exclusive_options(c, longopts, excl, excl_st);
391
392 switch (c) {
393 case 'a':
394 do_all = true;
395 break;
396 case 't':
397 namespace_target_pid =
398 strtoul_or_err(optarg, _("failed to parse pid"));
399 break;
400 case 'm':
401 if (optarg)
402 open_namespace_fd(CLONE_NEWNS, optarg);
403 else
404 namespaces |= CLONE_NEWNS;
405 break;
406 case 'u':
407 if (optarg)
408 open_namespace_fd(CLONE_NEWUTS, optarg);
409 else
410 namespaces |= CLONE_NEWUTS;
411 break;
412 case 'i':
413 if (optarg)
414 open_namespace_fd(CLONE_NEWIPC, optarg);
415 else
416 namespaces |= CLONE_NEWIPC;
417 break;
418 case 'n':
419 if (optarg)
420 open_namespace_fd(CLONE_NEWNET, optarg);
421 else
422 namespaces |= CLONE_NEWNET;
423 break;
424 case 'p':
425 if (optarg)
426 open_namespace_fd(CLONE_NEWPID, optarg);
427 else
428 namespaces |= CLONE_NEWPID;
429 break;
430 case 'C':
431 if (optarg)
432 open_namespace_fd(CLONE_NEWCGROUP, optarg);
433 else
434 namespaces |= CLONE_NEWCGROUP;
435 break;
436 case 'U':
437 if (optarg)
438 open_namespace_fd(CLONE_NEWUSER, optarg);
439 else
440 namespaces |= CLONE_NEWUSER;
441 break;
442 case 'T':
443 if (optarg)
444 open_namespace_fd(CLONE_NEWTIME, optarg);
445 else
446 namespaces |= CLONE_NEWTIME;
447 break;
448 case 'S':
449 if (strcmp(optarg, "follow") == 0)
450 do_uid = true;
451 else
452 uid = strtoul_or_err(optarg, _("failed to parse uid"));
453 force_uid = true;
454 break;
455 case 'G':
456 if (strcmp(optarg, "follow") == 0)
457 do_gid = true;
458 else
459 gid = strtoul_or_err(optarg, _("failed to parse gid"));
460 force_gid = true;
461 break;
462 case 'F':
463 do_fork = 0;
464 break;
465 case 'c':
466 do_join_cgroup = true;
467 break;
468 case 'r':
469 if (optarg)
470 open_target_fd(&root_fd, "root", optarg);
471 else
472 do_rd = true;
473 break;
474 case 'w':
475 if (optarg)
476 open_target_fd(&wd_fd, "cwd", optarg);
477 else
478 do_wd = true;
479 break;
480 case 'W':
481 wdns = optarg;
482 break;
483 case 'e':
484 do_env = true;
485 break;
486 case OPT_PRESERVE_CRED:
487 preserve_cred = 1;
488 break;
489 case OPT_KEEPCAPS:
490 keepcaps = 1;
491 break;
492 case OPT_USER_PARENT:
493 do_user_parent = true;
494 break;
495 #ifdef HAVE_LIBSELINUX
496 case 'Z':
497 selinux = 1;
498 break;
499 #endif
500 case 'h':
501 usage();
502 case 'V':
503 print_version(EXIT_SUCCESS);
504 default:
505 errtryhelp(EXIT_FAILURE);
506 }
507 }
508
509 #ifdef HAVE_LIBSELINUX
510 if (selinux && is_selinux_enabled() > 0) {
511 char *scon = NULL;
512
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);
520 freecon(scon);
521 }
522 #endif
523
524 if (do_all) {
525 if (!namespace_target_pid)
526 errx(EXIT_FAILURE, _("no target PID specified for --all"));
527 for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
528 if (nsfile->fd >= 0)
529 continue; /* namespace already specified */
530
531 if (!is_usable_namespace(namespace_target_pid, nsfile))
532 continue;
533
534 namespaces |= nsfile->nstype;
535 }
536 }
537
538 /*
539 * Open remaining namespace and directory descriptors.
540 */
541 for (nsfile = namespace_files; nsfile->nstype; nsfile++)
542 if (nsfile->nstype & namespaces)
543 open_namespace_fd(nsfile->nstype, NULL);
544 if (do_rd)
545 open_target_fd(&root_fd, "root", NULL);
546 if (do_wd)
547 open_target_fd(&wd_fd, "cwd", NULL);
548 if (do_env)
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) {
553 if (!is_cgroup2())
554 errx(EXIT_FAILURE, _("--join-cgroup is only supported in cgroup v2"));
555 open_cgroup_procs();
556 }
557
558 /*
559 * Get parent userns from any available ns.
560 */
561 if (do_user_parent)
562 set_parent_user_ns_fd();
563
564 /*
565 * Update namespaces variable to contain all requested namespaces
566 */
567 for (nsfile = namespace_files; nsfile->nstype; nsfile++) {
568 if (nsfile->fd < 0)
569 continue;
570 namespaces |= nsfile->nstype;
571 }
572
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;
577
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)
581 setgroups_nerrs++;
582 }
583
584 /*
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).
591 */
592 for (pass = 0; pass < 2; pass ++) {
593 for (nsfile = namespace_files + 1 - pass; nsfile->nstype; nsfile++) {
594 if (nsfile->fd < 0)
595 continue;
596 if (nsfile->nstype == CLONE_NEWPID && do_fork == -1)
597 do_fork = 1;
598 if (setns(nsfile->fd, nsfile->nstype)) {
599 if (pass != 0)
600 err(EXIT_FAILURE,
601 _("reassociate to namespace '%s' failed"),
602 nsfile->name);
603 else
604 continue;
605 }
606
607 close(nsfile->fd);
608 nsfile->fd = -1;
609 }
610 }
611
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);
615 if (wd_fd < 0)
616 err(EXIT_FAILURE,
617 _("cannot open current working directory"));
618 }
619
620 /* Change the root directory */
621 if (root_fd >= 0) {
622 if (fchdir(root_fd) < 0)
623 err(EXIT_FAILURE,
624 _("change directory by root file descriptor failed"));
625
626 if (chroot(".") < 0)
627 err(EXIT_FAILURE, _("chroot failed"));
628 if (chdir("/"))
629 err(EXIT_FAILURE, _("cannot change directory to %s"), "/");
630
631 close(root_fd);
632 root_fd = -1;
633 }
634
635 /* working directory specified as in-namespace path */
636 if (wdns) {
637 wd_fd = open(wdns, O_RDONLY);
638 if (wd_fd < 0)
639 err(EXIT_FAILURE,
640 _("cannot open current working directory"));
641 }
642
643 /* Change the working directory */
644 if (wd_fd >= 0) {
645 if (fchdir(wd_fd) < 0)
646 err(EXIT_FAILURE,
647 _("change directory by working directory file descriptor failed"));
648
649 close(wd_fd);
650 wd_fd = -1;
651 }
652
653 /* Pass environment variables of the target process to the spawned process */
654 if (env_fd >= 0) {
655 if ((envls = env_from_fd(env_fd)) == NULL)
656 err(EXIT_FAILURE, _("failed to get environment variables"));
657 clearenv();
658 if (env_list_setenv(envls) < 0)
659 err(EXIT_FAILURE, _("failed to set environment variables"));
660 env_list_free(envls);
661 close(env_fd);
662 }
663
664 // Join into the target cgroup
665 if (cgroup_procs_fd >= 0)
666 join_into_cgroup();
667
668 if (uid_gid_fd >= 0) {
669 struct stat st;
670
671 if (fstat(uid_gid_fd, &st) > 0)
672 err(EXIT_FAILURE, _("can not get process stat"));
673
674 close(uid_gid_fd);
675 uid_gid_fd = -1;
676
677 if (do_uid)
678 uid = st.st_uid;
679 if (do_gid)
680 gid = st.st_gid;
681 }
682
683 if (do_fork == 1)
684 continue_as_child();
685
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"));
693 }
694
695 if (keepcaps && (namespaces & CLONE_NEWUSER))
696 cap_permitted_to_ambient();
697
698 if (optind < argc) {
699 execvp(argv[optind], argv + optind);
700 errexec(argv[optind]);
701 }
702 exec_shell();
703 }