]> git.ipfire.org Git - thirdparty/util-linux.git/blob - sys-utils/unshare.c
Merge branch 'spelling' of https://github.com/jwilk-forks/util-linux
[thirdparty/util-linux.git] / sys-utils / unshare.c
1 /*
2 * unshare(1) - command-line interface for unshare(2)
3 *
4 * Copyright (C) 2009 Mikhail Gusarov <dottedmag@dottedmag.net>
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; either version 2, or (at your option) any
9 * later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; if not, write to the Free Software Foundation, Inc.,
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include <errno.h>
22 #include <getopt.h>
23 #include <sched.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <unistd.h>
27 #include <sys/wait.h>
28 #include <sys/mount.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <sys/prctl.h>
32 #include <grp.h>
33
34 /* we only need some defines missing in sys/mount.h, no libmount linkage */
35 #include <libmount.h>
36
37 #include "nls.h"
38 #include "c.h"
39 #include "closestream.h"
40 #include "namespace.h"
41 #include "exec_shell.h"
42 #include "xalloc.h"
43 #include "pathnames.h"
44 #include "all-io.h"
45 #include "signames.h"
46 #include "strutils.h"
47
48 /* synchronize parent and child by pipe */
49 #define PIPE_SYNC_BYTE 0x06
50
51 /* 'private' is kernel default */
52 #define UNSHARE_PROPAGATION_DEFAULT (MS_REC | MS_PRIVATE)
53
54 /* /proc namespace files and mountpoints for binds */
55 static struct namespace_file {
56 int type; /* CLONE_NEW* */
57 const char *name; /* ns/<type> */
58 const char *target; /* user specified target for bind mount */
59 } namespace_files[] = {
60 { .type = CLONE_NEWUSER, .name = "ns/user" },
61 { .type = CLONE_NEWCGROUP,.name = "ns/cgroup" },
62 { .type = CLONE_NEWIPC, .name = "ns/ipc" },
63 { .type = CLONE_NEWUTS, .name = "ns/uts" },
64 { .type = CLONE_NEWNET, .name = "ns/net" },
65 { .type = CLONE_NEWPID, .name = "ns/pid" },
66 { .type = CLONE_NEWNS, .name = "ns/mnt" },
67 { .name = NULL }
68 };
69
70 static int npersists; /* number of persistent namespaces */
71
72
73 enum {
74 SETGROUPS_NONE = -1,
75 SETGROUPS_DENY = 0,
76 SETGROUPS_ALLOW = 1,
77 };
78
79 static const char *setgroups_strings[] =
80 {
81 [SETGROUPS_DENY] = "deny",
82 [SETGROUPS_ALLOW] = "allow"
83 };
84
85 static int setgroups_str2id(const char *str)
86 {
87 size_t i;
88
89 for (i = 0; i < ARRAY_SIZE(setgroups_strings); i++)
90 if (strcmp(str, setgroups_strings[i]) == 0)
91 return i;
92
93 errx(EXIT_FAILURE, _("unsupported --setgroups argument '%s'"), str);
94 }
95
96 static void setgroups_control(int action)
97 {
98 const char *file = _PATH_PROC_SETGROUPS;
99 const char *cmd;
100 int fd;
101
102 if (action < 0 || (size_t) action >= ARRAY_SIZE(setgroups_strings))
103 return;
104 cmd = setgroups_strings[action];
105
106 fd = open(file, O_WRONLY);
107 if (fd < 0) {
108 if (errno == ENOENT)
109 return;
110 err(EXIT_FAILURE, _("cannot open %s"), file);
111 }
112
113 if (write_all(fd, cmd, strlen(cmd)))
114 err(EXIT_FAILURE, _("write failed %s"), file);
115 close(fd);
116 }
117
118 static void map_id(const char *file, uint32_t from, uint32_t to)
119 {
120 char *buf;
121 int fd;
122
123 fd = open(file, O_WRONLY);
124 if (fd < 0)
125 err(EXIT_FAILURE, _("cannot open %s"), file);
126
127 xasprintf(&buf, "%u %u 1", from, to);
128 if (write_all(fd, buf, strlen(buf)))
129 err(EXIT_FAILURE, _("write failed %s"), file);
130 free(buf);
131 close(fd);
132 }
133
134 static unsigned long parse_propagation(const char *str)
135 {
136 size_t i;
137 static const struct prop_opts {
138 const char *name;
139 unsigned long flag;
140 } opts[] = {
141 { "slave", MS_REC | MS_SLAVE },
142 { "private", MS_REC | MS_PRIVATE },
143 { "shared", MS_REC | MS_SHARED },
144 { "unchanged", 0 }
145 };
146
147 for (i = 0; i < ARRAY_SIZE(opts); i++) {
148 if (strcmp(opts[i].name, str) == 0)
149 return opts[i].flag;
150 }
151
152 errx(EXIT_FAILURE, _("unsupported propagation mode: %s"), str);
153 }
154
155 static void set_propagation(unsigned long flags)
156 {
157 if (flags == 0)
158 return;
159
160 if (mount("none", "/", NULL, flags, NULL) != 0)
161 err(EXIT_FAILURE, _("cannot change root filesystem propagation"));
162 }
163
164
165 static int set_ns_target(int type, const char *path)
166 {
167 struct namespace_file *ns;
168
169 for (ns = namespace_files; ns->name; ns++) {
170 if (ns->type != type)
171 continue;
172 ns->target = path;
173 npersists++;
174 return 0;
175 }
176
177 return -EINVAL;
178 }
179
180 static int bind_ns_files(pid_t pid)
181 {
182 struct namespace_file *ns;
183 char src[PATH_MAX];
184
185 for (ns = namespace_files; ns->name; ns++) {
186 if (!ns->target)
187 continue;
188
189 snprintf(src, sizeof(src), "/proc/%u/%s", (unsigned) pid, ns->name);
190
191 if (mount(src, ns->target, NULL, MS_BIND, NULL) != 0)
192 err(EXIT_FAILURE, _("mount %s on %s failed"), src, ns->target);
193 }
194
195 return 0;
196 }
197
198 static ino_t get_mnt_ino(pid_t pid)
199 {
200 struct stat st;
201 char path[PATH_MAX];
202
203 snprintf(path, sizeof(path), "/proc/%u/ns/mnt", (unsigned) pid);
204
205 if (stat(path, &st) != 0)
206 err(EXIT_FAILURE, _("cannot stat %s"), path);
207 return st.st_ino;
208 }
209
210 static void bind_ns_files_from_child(pid_t *child, int fds[2])
211 {
212 char ch;
213 pid_t ppid = getpid();
214 ino_t ino = get_mnt_ino(ppid);
215
216 if (pipe(fds) < 0)
217 err(EXIT_FAILURE, _("pipe failed"));
218
219 *child = fork();
220
221 switch (*child) {
222 case -1:
223 err(EXIT_FAILURE, _("fork failed"));
224
225 case 0: /* child */
226 close(fds[1]);
227 fds[1] = -1;
228
229 /* wait for parent */
230 if (read_all(fds[0], &ch, 1) != 1 && ch != PIPE_SYNC_BYTE)
231 err(EXIT_FAILURE, _("failed to read pipe"));
232 if (get_mnt_ino(ppid) == ino)
233 exit(EXIT_FAILURE);
234 bind_ns_files(ppid);
235 exit(EXIT_SUCCESS);
236 break;
237
238 default: /* parent */
239 close(fds[0]);
240 fds[0] = -1;
241 break;
242 }
243 }
244
245 static void __attribute__((__noreturn__)) usage(void)
246 {
247 FILE *out = stdout;
248
249 fputs(USAGE_HEADER, out);
250 fprintf(out, _(" %s [options] [<program> [<argument>...]]\n"),
251 program_invocation_short_name);
252
253 fputs(USAGE_SEPARATOR, out);
254 fputs(_("Run a program with some namespaces unshared from the parent.\n"), out);
255
256 fputs(USAGE_OPTIONS, out);
257 fputs(_(" -m, --mount[=<file>] unshare mounts namespace\n"), out);
258 fputs(_(" -u, --uts[=<file>] unshare UTS namespace (hostname etc)\n"), out);
259 fputs(_(" -i, --ipc[=<file>] unshare System V IPC namespace\n"), out);
260 fputs(_(" -n, --net[=<file>] unshare network namespace\n"), out);
261 fputs(_(" -p, --pid[=<file>] unshare pid namespace\n"), out);
262 fputs(_(" -U, --user[=<file>] unshare user namespace\n"), out);
263 fputs(_(" -C, --cgroup[=<file>] unshare cgroup namespace\n"), out);
264 fputs(USAGE_SEPARATOR, out);
265 fputs(_(" -f, --fork fork before launching <program>\n"), out);
266 fputs(_(" -r, --map-root-user map current user to root (implies --user)\n"), out);
267 fputs(USAGE_SEPARATOR, out);
268 fputs(_(" --kill-child[=<signame>] when dying, kill the forked child (implies --fork)\n"
269 " defaults to SIGKILL\n"), out);
270 fputs(_(" --mount-proc[=<dir>] mount proc filesystem first (implies --mount)\n"), out);
271 fputs(_(" --propagation slave|shared|private|unchanged\n"
272 " modify mount propagation in mount namespace\n"), out);
273 fputs(_(" --setgroups allow|deny control the setgroups syscall in user namespaces\n"), out);
274 fputs(USAGE_SEPARATOR, out);
275 fputs(_(" -R, --root=<dir> run the command with root directory set to <dir>\n"), out);
276 fputs(_(" -w, --wd=<dir> change working directory to <dir>\n"), out);
277 fputs(_(" -S, --setuid <uid> set uid in entered namespace\n"), out);
278 fputs(_(" -G, --setgid <gid> set gid in entered namespace\n"), out);
279
280 fputs(USAGE_SEPARATOR, out);
281 printf(USAGE_HELP_OPTIONS(27));
282 printf(USAGE_MAN_TAIL("unshare(1)"));
283
284 exit(EXIT_SUCCESS);
285 }
286
287 int main(int argc, char *argv[])
288 {
289 enum {
290 OPT_MOUNTPROC = CHAR_MAX + 1,
291 OPT_PROPAGATION,
292 OPT_SETGROUPS,
293 OPT_KILLCHILD,
294 };
295 static const struct option longopts[] = {
296 { "help", no_argument, NULL, 'h' },
297 { "version", no_argument, NULL, 'V' },
298
299 { "mount", optional_argument, NULL, 'm' },
300 { "uts", optional_argument, NULL, 'u' },
301 { "ipc", optional_argument, NULL, 'i' },
302 { "net", optional_argument, NULL, 'n' },
303 { "pid", optional_argument, NULL, 'p' },
304 { "user", optional_argument, NULL, 'U' },
305 { "cgroup", optional_argument, NULL, 'C' },
306
307 { "fork", no_argument, NULL, 'f' },
308 { "kill-child", optional_argument, NULL, OPT_KILLCHILD },
309 { "mount-proc", optional_argument, NULL, OPT_MOUNTPROC },
310 { "map-root-user", no_argument, NULL, 'r' },
311 { "propagation", required_argument, NULL, OPT_PROPAGATION },
312 { "setgroups", required_argument, NULL, OPT_SETGROUPS },
313 { "setuid", required_argument, NULL, 'S' },
314 { "setgid", required_argument, NULL, 'G' },
315 { "root", required_argument, NULL, 'R' },
316 { "wd", required_argument, NULL, 'w' },
317 { NULL, 0, NULL, 0 }
318 };
319
320 int setgrpcmd = SETGROUPS_NONE;
321 int unshare_flags = 0;
322 int c, forkit = 0, maproot = 0;
323 int kill_child_signo = 0; /* 0 means --kill-child was not used */
324 const char *procmnt = NULL;
325 const char *newroot = NULL;
326 const char *newdir = NULL;
327 pid_t pid = 0;
328 int fds[2];
329 int status;
330 unsigned long propagation = UNSHARE_PROPAGATION_DEFAULT;
331 int force_uid = 0, force_gid = 0;
332 uid_t uid = 0, real_euid = geteuid();
333 gid_t gid = 0, real_egid = getegid();
334
335 setlocale(LC_ALL, "");
336 bindtextdomain(PACKAGE, LOCALEDIR);
337 textdomain(PACKAGE);
338 atexit(close_stdout);
339
340 while ((c = getopt_long(argc, argv, "+fhVmuinpCUrR:w:S:G:", longopts, NULL)) != -1) {
341 switch (c) {
342 case 'f':
343 forkit = 1;
344 break;
345 case 'h':
346 usage();
347 case 'V':
348 printf(UTIL_LINUX_VERSION);
349 return EXIT_SUCCESS;
350 case 'm':
351 unshare_flags |= CLONE_NEWNS;
352 if (optarg)
353 set_ns_target(CLONE_NEWNS, optarg);
354 break;
355 case 'u':
356 unshare_flags |= CLONE_NEWUTS;
357 if (optarg)
358 set_ns_target(CLONE_NEWUTS, optarg);
359 break;
360 case 'i':
361 unshare_flags |= CLONE_NEWIPC;
362 if (optarg)
363 set_ns_target(CLONE_NEWIPC, optarg);
364 break;
365 case 'n':
366 unshare_flags |= CLONE_NEWNET;
367 if (optarg)
368 set_ns_target(CLONE_NEWNET, optarg);
369 break;
370 case 'p':
371 unshare_flags |= CLONE_NEWPID;
372 if (optarg)
373 set_ns_target(CLONE_NEWPID, optarg);
374 break;
375 case 'U':
376 unshare_flags |= CLONE_NEWUSER;
377 if (optarg)
378 set_ns_target(CLONE_NEWUSER, optarg);
379 break;
380 case 'C':
381 unshare_flags |= CLONE_NEWCGROUP;
382 if (optarg)
383 set_ns_target(CLONE_NEWCGROUP, optarg);
384 break;
385 case OPT_MOUNTPROC:
386 unshare_flags |= CLONE_NEWNS;
387 procmnt = optarg ? optarg : "/proc";
388 break;
389 case 'r':
390 unshare_flags |= CLONE_NEWUSER;
391 maproot = 1;
392 break;
393 case OPT_SETGROUPS:
394 setgrpcmd = setgroups_str2id(optarg);
395 break;
396 case OPT_PROPAGATION:
397 propagation = parse_propagation(optarg);
398 break;
399 case OPT_KILLCHILD:
400 forkit = 1;
401 if (optarg) {
402 if ((kill_child_signo = signame_to_signum(optarg)) < 0)
403 errx(EXIT_FAILURE, _("unknown signal: %s"),
404 optarg);
405 } else {
406 kill_child_signo = SIGKILL;
407 }
408 break;
409 case 'S':
410 uid = strtoul_or_err(optarg, _("failed to parse uid"));
411 force_uid = 1;
412 break;
413 case 'G':
414 gid = strtoul_or_err(optarg, _("failed to parse gid"));
415 force_gid = 1;
416 break;
417 case 'R':
418 newroot = optarg;
419 break;
420 case 'w':
421 newdir = optarg;
422 break;
423 default:
424 errtryhelp(EXIT_FAILURE);
425 }
426 }
427
428 if (npersists && (unshare_flags & CLONE_NEWNS))
429 bind_ns_files_from_child(&pid, fds);
430
431 if (-1 == unshare(unshare_flags))
432 err(EXIT_FAILURE, _("unshare failed"));
433
434 if (npersists) {
435 if (pid && (unshare_flags & CLONE_NEWNS)) {
436 int rc;
437 char ch = PIPE_SYNC_BYTE;
438
439 /* signal child we are ready */
440 write_all(fds[1], &ch, 1);
441 close(fds[1]);
442 fds[1] = -1;
443
444 /* wait for bind_ns_files_from_child() */
445 do {
446 rc = waitpid(pid, &status, 0);
447 if (rc < 0) {
448 if (errno == EINTR)
449 continue;
450 err(EXIT_FAILURE, _("waitpid failed"));
451 }
452 if (WIFEXITED(status) &&
453 WEXITSTATUS(status) != EXIT_SUCCESS)
454 return WEXITSTATUS(status);
455 } while (rc < 0);
456 } else
457 /* simple way, just bind */
458 bind_ns_files(getpid());
459 }
460
461 if (forkit) {
462 pid = fork();
463
464 switch(pid) {
465 case -1:
466 err(EXIT_FAILURE, _("fork failed"));
467 case 0: /* child */
468 break;
469 default: /* parent */
470 if (waitpid(pid, &status, 0) == -1)
471 err(EXIT_FAILURE, _("waitpid failed"));
472 if (WIFEXITED(status))
473 return WEXITSTATUS(status);
474 else if (WIFSIGNALED(status))
475 kill(getpid(), WTERMSIG(status));
476 err(EXIT_FAILURE, _("child exit failed"));
477 }
478 }
479
480 if (kill_child_signo != 0 && prctl(PR_SET_PDEATHSIG, kill_child_signo) < 0)
481 err(EXIT_FAILURE, "prctl failed");
482
483 if (maproot) {
484 if (setgrpcmd == SETGROUPS_ALLOW)
485 errx(EXIT_FAILURE, _("options --setgroups=allow and "
486 "--map-root-user are mutually exclusive"));
487
488 /* since Linux 3.19 unprivileged writing of /proc/self/gid_map
489 * has s been disabled unless /proc/self/setgroups is written
490 * first to permanently disable the ability to call setgroups
491 * in that user namespace. */
492 setgroups_control(SETGROUPS_DENY);
493 map_id(_PATH_PROC_UIDMAP, 0, real_euid);
494 map_id(_PATH_PROC_GIDMAP, 0, real_egid);
495
496 } else if (setgrpcmd != SETGROUPS_NONE)
497 setgroups_control(setgrpcmd);
498
499 if ((unshare_flags & CLONE_NEWNS) && propagation)
500 set_propagation(propagation);
501
502 if (newroot) {
503 if (chroot(newroot) != 0)
504 err(EXIT_FAILURE,
505 _("cannot change root directory to '%s'"), newroot);
506 newdir = newdir ?: "/";
507 }
508 if (newdir && chdir(newdir))
509 err(EXIT_FAILURE, _("cannot chdir to '%s'"), newdir);
510
511 if (procmnt) {
512 if (!newroot && mount("none", procmnt, NULL, MS_PRIVATE|MS_REC, NULL) != 0)
513 err(EXIT_FAILURE, _("umount %s failed"), procmnt);
514 if (mount("proc", procmnt, "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) != 0)
515 err(EXIT_FAILURE, _("mount %s failed"), procmnt);
516 }
517
518 if (force_gid) {
519 if (setgroups(0, NULL) != 0) /* drop supplementary groups */
520 err(EXIT_FAILURE, _("setgroups failed"));
521 if (setgid(gid) < 0) /* change GID */
522 err(EXIT_FAILURE, _("setgid failed"));
523 }
524 if (force_uid && setuid(uid) < 0) /* change UID */
525 err(EXIT_FAILURE, _("setuid failed"));
526
527 if (optind < argc) {
528 execvp(argv[optind], argv + optind);
529 errexec(argv[optind]);
530 }
531 exec_shell();
532 }