]> git.ipfire.org Git - thirdparty/util-linux.git/blame - sys-utils/unshare.c
libmount: don't use sscanf() for swaps parsing
[thirdparty/util-linux.git] / sys-utils / unshare.c
CommitLineData
4205f1fd
MG
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.,
7cebf0bb 18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
4205f1fd
MG
19 */
20
4205f1fd
MG
21#include <errno.h>
22#include <getopt.h>
23#include <sched.h>
24#include <stdio.h>
25#include <stdlib.h>
26#include <unistd.h>
5088ec33 27#include <sys/wait.h>
6728ca10 28#include <sys/mount.h>
c84f2590
KZ
29#include <sys/types.h>
30#include <sys/stat.h>
8e8f0fa5 31#include <sys/prctl.h>
c84f2590 32
d754315c
RM
33/* we only need some defines missing in sys/mount.h, no libmount linkage */
34#include <libmount.h>
35
4205f1fd 36#include "nls.h"
eb76ca98 37#include "c.h"
efb8854f 38#include "closestream.h"
c91280a4 39#include "namespace.h"
57580694 40#include "exec_shell.h"
4da21e37
LR
41#include "xalloc.h"
42#include "pathnames.h"
43#include "all-io.h"
8b39a17c 44#include "signames.h"
4da21e37 45
99fcafdf
YK
46/* synchronize parent and child by pipe */
47#define PIPE_SYNC_BYTE 0x06
48
f0f22e9c
KZ
49/* 'private' is kernel default */
50#define UNSHARE_PROPAGATION_DEFAULT (MS_REC | MS_PRIVATE)
51
0490a6ca
KZ
52/* /proc namespace files and mountpoints for binds */
53static struct namespace_file {
54 int type; /* CLONE_NEW* */
55 const char *name; /* ns/<type> */
56 const char *target; /* user specified target for bind mount */
57} namespace_files[] = {
f9e7b66d
SH
58 { .type = CLONE_NEWUSER, .name = "ns/user" },
59 { .type = CLONE_NEWCGROUP,.name = "ns/cgroup" },
60 { .type = CLONE_NEWIPC, .name = "ns/ipc" },
61 { .type = CLONE_NEWUTS, .name = "ns/uts" },
62 { .type = CLONE_NEWNET, .name = "ns/net" },
63 { .type = CLONE_NEWPID, .name = "ns/pid" },
64 { .type = CLONE_NEWNS, .name = "ns/mnt" },
0490a6ca
KZ
65 { .name = NULL }
66};
67
68static int npersists; /* number of persistent namespaces */
69
70
fbceefde
KZ
71enum {
72 SETGROUPS_NONE = -1,
73 SETGROUPS_DENY = 0,
74 SETGROUPS_ALLOW = 1,
75};
76
77static const char *setgroups_strings[] =
78{
79 [SETGROUPS_DENY] = "deny",
80 [SETGROUPS_ALLOW] = "allow"
81};
82
83static int setgroups_str2id(const char *str)
84{
85 size_t i;
86
87 for (i = 0; i < ARRAY_SIZE(setgroups_strings); i++)
88 if (strcmp(str, setgroups_strings[i]) == 0)
89 return i;
90
91 errx(EXIT_FAILURE, _("unsupported --setgroups argument '%s'"), str);
92}
93
94static void setgroups_control(int action)
0bf15941
EB
95{
96 const char *file = _PATH_PROC_SETGROUPS;
fbceefde 97 const char *cmd;
0bf15941
EB
98 int fd;
99
fbceefde
KZ
100 if (action < 0 || (size_t) action >= ARRAY_SIZE(setgroups_strings))
101 return;
102 cmd = setgroups_strings[action];
103
0bf15941
EB
104 fd = open(file, O_WRONLY);
105 if (fd < 0) {
106 if (errno == ENOENT)
107 return;
7ff635bf 108 err(EXIT_FAILURE, _("cannot open %s"), file);
0bf15941
EB
109 }
110
fbceefde 111 if (write_all(fd, cmd, strlen(cmd)))
0bf15941
EB
112 err(EXIT_FAILURE, _("write failed %s"), file);
113 close(fd);
114}
115
4da21e37
LR
116static void map_id(const char *file, uint32_t from, uint32_t to)
117{
118 char *buf;
119 int fd;
120
121 fd = open(file, O_WRONLY);
122 if (fd < 0)
123 err(EXIT_FAILURE, _("cannot open %s"), file);
124
125 xasprintf(&buf, "%u %u 1", from, to);
126 if (write_all(fd, buf, strlen(buf)))
127 err(EXIT_FAILURE, _("write failed %s"), file);
128 free(buf);
129 close(fd);
130}
4205f1fd 131
f0f22e9c
KZ
132static unsigned long parse_propagation(const char *str)
133{
134 size_t i;
135 static const struct prop_opts {
136 const char *name;
137 unsigned long flag;
138 } opts[] = {
139 { "slave", MS_REC | MS_SLAVE },
140 { "private", MS_REC | MS_PRIVATE },
141 { "shared", MS_REC | MS_SHARED },
142 { "unchanged", 0 }
143 };
144
145 for (i = 0; i < ARRAY_SIZE(opts); i++) {
146 if (strcmp(opts[i].name, str) == 0)
147 return opts[i].flag;
148 }
149
150 errx(EXIT_FAILURE, _("unsupported propagation mode: %s"), str);
151}
152
153static void set_propagation(unsigned long flags)
154{
155 if (flags == 0)
156 return;
157
158 if (mount("none", "/", NULL, flags, NULL) != 0)
159 err(EXIT_FAILURE, _("cannot change root filesystem propagation"));
160}
161
0490a6ca
KZ
162
163static int set_ns_target(int type, const char *path)
164{
165 struct namespace_file *ns;
166
167 for (ns = namespace_files; ns->name; ns++) {
168 if (ns->type != type)
169 continue;
170 ns->target = path;
171 npersists++;
172 return 0;
173 }
174
175 return -EINVAL;
176}
177
178static int bind_ns_files(pid_t pid)
179{
180 struct namespace_file *ns;
181 char src[PATH_MAX];
182
183 for (ns = namespace_files; ns->name; ns++) {
184 if (!ns->target)
185 continue;
186
187 snprintf(src, sizeof(src), "/proc/%u/%s", (unsigned) pid, ns->name);
188
189 if (mount(src, ns->target, NULL, MS_BIND, NULL) != 0)
190 err(EXIT_FAILURE, _("mount %s on %s failed"), src, ns->target);
191 }
192
193 return 0;
194}
195
c84f2590
KZ
196static ino_t get_mnt_ino(pid_t pid)
197{
198 struct stat st;
199 char path[PATH_MAX];
200
201 snprintf(path, sizeof(path), "/proc/%u/ns/mnt", (unsigned) pid);
202
203 if (stat(path, &st) != 0)
204 err(EXIT_FAILURE, _("cannot stat %s"), path);
205 return st.st_ino;
206}
207
99fcafdf 208static void bind_ns_files_from_child(pid_t *child, int fds[2])
c84f2590 209{
99fcafdf 210 char ch;
c84f2590
KZ
211 pid_t ppid = getpid();
212 ino_t ino = get_mnt_ino(ppid);
213
99fcafdf
YK
214 if (pipe(fds) < 0)
215 err(EXIT_FAILURE, _("pipe failed"));
216
c84f2590
KZ
217 *child = fork();
218
99fcafdf 219 switch (*child) {
c84f2590
KZ
220 case -1:
221 err(EXIT_FAILURE, _("fork failed"));
99fcafdf 222
c84f2590 223 case 0: /* child */
99fcafdf
YK
224 close(fds[1]);
225 fds[1] = -1;
226
227 /* wait for parent */
228 if (read_all(fds[0], &ch, 1) != 1 && ch != PIPE_SYNC_BYTE)
229 err(EXIT_FAILURE, _("failed to read pipe"));
230 if (get_mnt_ino(ppid) == ino)
231 exit(EXIT_FAILURE);
c84f2590
KZ
232 bind_ns_files(ppid);
233 exit(EXIT_SUCCESS);
234 break;
99fcafdf 235
c84f2590 236 default: /* parent */
99fcafdf
YK
237 close(fds[0]);
238 fds[0] = -1;
c84f2590
KZ
239 break;
240 }
241}
242
fa2cd89a 243static void __attribute__((__noreturn__)) usage(void)
4205f1fd 244{
fa2cd89a 245 FILE *out = stdout;
4205f1fd 246
6a87798a 247 fputs(USAGE_HEADER, out);
b5672517 248 fprintf(out, _(" %s [options] [<program> [<argument>...]]\n"),
298dc4ff 249 program_invocation_short_name);
4205f1fd 250
451dbcfa
BS
251 fputs(USAGE_SEPARATOR, out);
252 fputs(_("Run a program with some namespaces unshared from the parent.\n"), out);
253
6a87798a 254 fputs(USAGE_OPTIONS, out);
0490a6ca
KZ
255 fputs(_(" -m, --mount[=<file>] unshare mounts namespace\n"), out);
256 fputs(_(" -u, --uts[=<file>] unshare UTS namespace (hostname etc)\n"), out);
257 fputs(_(" -i, --ipc[=<file>] unshare System V IPC namespace\n"), out);
258 fputs(_(" -n, --net[=<file>] unshare network namespace\n"), out);
259 fputs(_(" -p, --pid[=<file>] unshare pid namespace\n"), out);
260 fputs(_(" -U, --user[=<file>] unshare user namespace\n"), out);
f9e7b66d 261 fputs(_(" -C, --cgroup[=<file>] unshare cgroup namespace\n"), out);
da639217 262 fputs(USAGE_SEPARATOR, out);
6728ca10 263 fputs(_(" -f, --fork fork before launching <program>\n"), out);
4da21e37 264 fputs(_(" -r, --map-root-user map current user to root (implies --user)\n"), out);
da639217
KZ
265 fputs(USAGE_SEPARATOR, out);
266 fputs(_(" --kill-child[=<signame>] when dying, kill the forked child (implies --fork)\n"
267 " defaults to SIGKILL\n"), out);
268 fputs(_(" --mount-proc[=<dir>] mount proc filesystem first (implies --mount)\n"), out);
269 fputs(_(" --propagation slave|shared|private|unchanged\n"
f0f22e9c 270 " modify mount propagation in mount namespace\n"), out);
da639217 271 fputs(_(" --setgroups allow|deny control the setgroups syscall in user namespaces\n"), out);
4205f1fd 272
6a87798a 273 fputs(USAGE_SEPARATOR, out);
f45f3ec3
RM
274 printf(USAGE_HELP_OPTIONS(27));
275 printf(USAGE_MAN_TAIL("unshare(1)"));
6a87798a 276
fa2cd89a 277 exit(EXIT_SUCCESS);
4205f1fd
MG
278}
279
280int main(int argc, char *argv[])
281{
6728ca10 282 enum {
fbceefde 283 OPT_MOUNTPROC = CHAR_MAX + 1,
f0f22e9c 284 OPT_PROPAGATION,
8e8f0fa5
NH
285 OPT_SETGROUPS,
286 OPT_KILLCHILD
6728ca10 287 };
6c7d5ae9 288 static const struct option longopts[] = {
87918040
SK
289 { "help", no_argument, NULL, 'h' },
290 { "version", no_argument, NULL, 'V' },
291
292 { "mount", optional_argument, NULL, 'm' },
293 { "uts", optional_argument, NULL, 'u' },
294 { "ipc", optional_argument, NULL, 'i' },
295 { "net", optional_argument, NULL, 'n' },
296 { "pid", optional_argument, NULL, 'p' },
297 { "user", optional_argument, NULL, 'U' },
298 { "cgroup", optional_argument, NULL, 'C' },
299
300 { "fork", no_argument, NULL, 'f' },
8b39a17c 301 { "kill-child", optional_argument, NULL, OPT_KILLCHILD },
87918040
SK
302 { "mount-proc", optional_argument, NULL, OPT_MOUNTPROC },
303 { "map-root-user", no_argument, NULL, 'r' },
304 { "propagation", required_argument, NULL, OPT_PROPAGATION },
305 { "setgroups", required_argument, NULL, OPT_SETGROUPS },
306 { NULL, 0, NULL, 0 }
4205f1fd
MG
307 };
308
fbceefde 309 int setgrpcmd = SETGROUPS_NONE;
4205f1fd 310 int unshare_flags = 0;
4da21e37 311 int c, forkit = 0, maproot = 0;
8b39a17c 312 int kill_child_signo = 0; /* 0 means --kill-child was not used */
6728ca10 313 const char *procmnt = NULL;
c84f2590 314 pid_t pid = 0;
99fcafdf 315 int fds[2];
c84f2590 316 int status;
f0f22e9c 317 unsigned long propagation = UNSHARE_PROPAGATION_DEFAULT;
4da21e37 318 uid_t real_euid = geteuid();
f4d37838 319 gid_t real_egid = getegid();
4205f1fd 320
999ac5e2 321 setlocale(LC_ALL, "");
4205f1fd
MG
322 bindtextdomain(PACKAGE, LOCALEDIR);
323 textdomain(PACKAGE);
efb8854f 324 atexit(close_stdout);
4205f1fd 325
f9e7b66d 326 while ((c = getopt_long(argc, argv, "+fhVmuinpCUr", longopts, NULL)) != -1) {
2eefe517 327 switch (c) {
5088ec33
MF
328 case 'f':
329 forkit = 1;
330 break;
4205f1fd 331 case 'h':
fa2cd89a 332 usage();
6a87798a
SK
333 case 'V':
334 printf(UTIL_LINUX_VERSION);
335 return EXIT_SUCCESS;
4205f1fd 336 case 'm':
ef6acdb8 337 unshare_flags |= CLONE_NEWNS;
0490a6ca
KZ
338 if (optarg)
339 set_ns_target(CLONE_NEWNS, optarg);
4205f1fd
MG
340 break;
341 case 'u':
ef6acdb8 342 unshare_flags |= CLONE_NEWUTS;
0490a6ca
KZ
343 if (optarg)
344 set_ns_target(CLONE_NEWUTS, optarg);
4205f1fd
MG
345 break;
346 case 'i':
ef6acdb8 347 unshare_flags |= CLONE_NEWIPC;
0490a6ca
KZ
348 if (optarg)
349 set_ns_target(CLONE_NEWIPC, optarg);
4205f1fd
MG
350 break;
351 case 'n':
ef6acdb8 352 unshare_flags |= CLONE_NEWNET;
0490a6ca
KZ
353 if (optarg)
354 set_ns_target(CLONE_NEWNET, optarg);
4205f1fd 355 break;
bc7f9b95
EB
356 case 'p':
357 unshare_flags |= CLONE_NEWPID;
0490a6ca
KZ
358 if (optarg)
359 set_ns_target(CLONE_NEWPID, optarg);
bc7f9b95
EB
360 break;
361 case 'U':
362 unshare_flags |= CLONE_NEWUSER;
0490a6ca
KZ
363 if (optarg)
364 set_ns_target(CLONE_NEWUSER, optarg);
bc7f9b95 365 break;
f9e7b66d
SH
366 case 'C':
367 unshare_flags |= CLONE_NEWCGROUP;
368 if (optarg)
369 set_ns_target(CLONE_NEWCGROUP, optarg);
370 break;
6728ca10
KZ
371 case OPT_MOUNTPROC:
372 unshare_flags |= CLONE_NEWNS;
373 procmnt = optarg ? optarg : "/proc";
374 break;
4da21e37
LR
375 case 'r':
376 unshare_flags |= CLONE_NEWUSER;
377 maproot = 1;
378 break;
fbceefde
KZ
379 case OPT_SETGROUPS:
380 setgrpcmd = setgroups_str2id(optarg);
381 break;
f0f22e9c
KZ
382 case OPT_PROPAGATION:
383 propagation = parse_propagation(optarg);
384 break;
8e8f0fa5 385 case OPT_KILLCHILD:
8e8f0fa5 386 forkit = 1;
8b39a17c
NH
387 if (optarg) {
388 if ((kill_child_signo = signame_to_signum(optarg)) < 0)
389 errx(EXIT_FAILURE, _("unknown signal: %s"),
390 optarg);
391 } else {
392 kill_child_signo = SIGKILL;
393 }
8e8f0fa5 394 break;
4205f1fd 395 default:
677ec86c 396 errtryhelp(EXIT_FAILURE);
4205f1fd
MG
397 }
398 }
399
c84f2590 400 if (npersists && (unshare_flags & CLONE_NEWNS))
99fcafdf 401 bind_ns_files_from_child(&pid, fds);
c84f2590 402
2eefe517 403 if (-1 == unshare(unshare_flags))
4205f1fd
MG
404 err(EXIT_FAILURE, _("unshare failed"));
405
c84f2590
KZ
406 if (npersists) {
407 if (pid && (unshare_flags & CLONE_NEWNS)) {
c84f2590 408 int rc;
99fcafdf
YK
409 char ch = PIPE_SYNC_BYTE;
410
411 /* signal child we are ready */
412 write_all(fds[1], &ch, 1);
413 close(fds[1]);
414 fds[1] = -1;
c84f2590 415
99fcafdf 416 /* wait for bind_ns_files_from_child() */
c84f2590
KZ
417 do {
418 rc = waitpid(pid, &status, 0);
419 if (rc < 0) {
420 if (errno == EINTR)
421 continue;
422 err(EXIT_FAILURE, _("waitpid failed"));
423 }
424 if (WIFEXITED(status) &&
425 WEXITSTATUS(status) != EXIT_SUCCESS)
426 return WEXITSTATUS(status);
427 } while (rc < 0);
428 } else
429 /* simple way, just bind */
430 bind_ns_files(getpid());
431 }
432
5088ec33 433 if (forkit) {
c84f2590 434 pid = fork();
5088ec33
MF
435
436 switch(pid) {
437 case -1:
438 err(EXIT_FAILURE, _("fork failed"));
439 case 0: /* child */
440 break;
441 default: /* parent */
442 if (waitpid(pid, &status, 0) == -1)
443 err(EXIT_FAILURE, _("waitpid failed"));
444 if (WIFEXITED(status))
445 return WEXITSTATUS(status);
446 else if (WIFSIGNALED(status))
447 kill(getpid(), WTERMSIG(status));
448 err(EXIT_FAILURE, _("child exit failed"));
449 }
450 }
451
525a0ab2
KZ
452 if (kill_child_signo != 0 && prctl(PR_SET_PDEATHSIG, kill_child_signo) < 0)
453 err(EXIT_FAILURE, "prctl failed");
0490a6ca 454
4da21e37 455 if (maproot) {
fbceefde
KZ
456 if (setgrpcmd == SETGROUPS_ALLOW)
457 errx(EXIT_FAILURE, _("options --setgroups=allow and "
54fefa07 458 "--map-root-user are mutually exclusive"));
fbceefde
KZ
459
460 /* since Linux 3.19 unprivileged writing of /proc/self/gid_map
461 * has s been disabled unless /proc/self/setgroups is written
462 * first to permanently disable the ability to call setgroups
463 * in that user namespace. */
464 setgroups_control(SETGROUPS_DENY);
4da21e37
LR
465 map_id(_PATH_PROC_UIDMAP, 0, real_euid);
466 map_id(_PATH_PROC_GIDMAP, 0, real_egid);
fbceefde
KZ
467
468 } else if (setgrpcmd != SETGROUPS_NONE)
469 setgroups_control(setgrpcmd);
4da21e37 470
f0f22e9c
KZ
471 if ((unshare_flags & CLONE_NEWNS) && propagation)
472 set_propagation(propagation);
473
6728ca10
KZ
474 if (procmnt &&
475 (mount("none", procmnt, NULL, MS_PRIVATE|MS_REC, NULL) != 0 ||
476 mount("proc", procmnt, "proc", MS_NOSUID|MS_NOEXEC|MS_NODEV, NULL) != 0))
477 err(EXIT_FAILURE, _("mount %s failed"), procmnt);
478
57580694
ZJS
479 if (optind < argc) {
480 execvp(argv[optind], argv + optind);
fd777151 481 errexec(argv[optind]);
57580694
ZJS
482 }
483 exec_shell();
4205f1fd 484}