]>
Commit | Line | Data |
---|---|---|
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 */ |
53 | static 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 | ||
68 | static int npersists; /* number of persistent namespaces */ | |
69 | ||
70 | ||
fbceefde KZ |
71 | enum { |
72 | SETGROUPS_NONE = -1, | |
73 | SETGROUPS_DENY = 0, | |
74 | SETGROUPS_ALLOW = 1, | |
75 | }; | |
76 | ||
77 | static const char *setgroups_strings[] = | |
78 | { | |
79 | [SETGROUPS_DENY] = "deny", | |
80 | [SETGROUPS_ALLOW] = "allow" | |
81 | }; | |
82 | ||
83 | static 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 | ||
94 | static 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 |
116 | static 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 |
132 | static 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 | ||
153 | static 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 | |
163 | static 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 | ||
178 | static 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 |
196 | static 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 | 208 | static 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 | 243 | static 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 | ||
280 | int 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 | } |