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