]>
Commit | Line | Data |
---|---|---|
cc73685d | 1 | /* SPDX-License-Identifier: LGPL-2.1+ */ |
d06245b8 | 2 | |
d38dd64a CB |
3 | #ifndef _GNU_SOURCE |
4 | #define _GNU_SOURCE 1 | |
5 | #endif | |
7a401679 | 6 | #include <errno.h> |
d567a9a7 | 7 | #include <fcntl.h> |
633cb02a | 8 | #include <getopt.h> |
7a401679 | 9 | #include <libgen.h> |
d38dd64a | 10 | #include <netinet/in.h> |
7a401679 | 11 | #include <pwd.h> |
d567a9a7 | 12 | #include <sched.h> |
633cb02a | 13 | #include <signal.h> |
d567a9a7 | 14 | #include <stdio.h> |
7a401679 SG |
15 | #include <stdlib.h> |
16 | #include <string.h> | |
344c9d81 | 17 | #include <sys/eventfd.h> |
7a401679 | 18 | #include <sys/socket.h> |
633cb02a | 19 | #include <sys/types.h> |
20 | #include <sys/wait.h> | |
d38dd64a | 21 | #include <unistd.h> |
633cb02a | 22 | |
d567a9a7 | 23 | #include "arguments.h" |
4f66541c | 24 | #include "caps.h" |
d38dd64a | 25 | #include "config.h" |
59e1663a | 26 | #include "list.h" |
727b9b16 | 27 | #include "log.h" |
4f66541c | 28 | #include "namespace.h" |
9b57809d CB |
29 | #include "syscall_numbers.h" |
30 | #include "syscall_wrappers.h" | |
4f66541c | 31 | #include "utils.h" |
7a401679 | 32 | |
727b9b16 | 33 | lxc_log_define(lxc_unshare, lxc); |
34 | ||
35 | struct start_arg { | |
36 | char *const *args; | |
37 | int flags; | |
38 | uid_t uid; | |
39 | bool setuid; | |
40 | int want_default_mounts; | |
41 | int wait_fd; | |
42 | const char *want_hostname; | |
43 | }; | |
44 | ||
727b9b16 | 45 | static int my_parser(struct lxc_arguments *args, int c, char *arg); |
727b9b16 | 46 | static int get_namespace_flags(char *namespaces); |
a7547c5c | 47 | static bool lookup_user(const char *oparg, uid_t *uid); |
727b9b16 | 48 | static int mount_fs(const char *source, const char *target, const char *type); |
49 | static void lxc_setup_fs(void); | |
50 | static int do_start(void *arg); | |
59e1663a | 51 | static void free_ifname_list(void); |
727b9b16 | 52 | |
59e1663a | 53 | static struct lxc_list ifnames; |
727b9b16 | 54 | |
55 | static const struct option my_longopts[] = { | |
56 | {"namespaces", required_argument, 0, 's'}, | |
57 | {"user", required_argument, 0, 'u'}, | |
58 | {"hostname", required_argument, 0, 'H'}, | |
59 | {"ifname", required_argument, 0, 'i'}, | |
60 | {"daemon", no_argument, 0, 'd'}, | |
61 | {"remount", no_argument, 0, 'M'}, | |
62 | LXC_COMMON_OPTIONS | |
63 | }; | |
64 | ||
65 | static struct lxc_arguments my_args = { | |
66 | .progname = "lxc-unshare", | |
67 | .help = "\ | |
68 | -s NAMESPACES COMMAND\n\ | |
69 | \n\ | |
70 | lxc-unshare run a COMMAND in a new set of NAMESPACES\n\ | |
71 | \n\ | |
72 | Options :\n\ | |
73 | -s, --namespaces=FLAGS\n\ | |
74 | ORed list of flags to unshare:\n\ | |
75 | MOUNT, PID, UTSNAME, IPC, USER, NETWORK\n\ | |
76 | -u, --user=USERID\n\ | |
77 | new id to be set if -s USER is specified\n\ | |
78 | -H, --hostname=HOSTNAME\n\ | |
79 | Set the hostname in the container\n\ | |
80 | -i, --ifname=IFNAME\n\ | |
81 | Interface name to be moved into container (presumably with NETWORK unsharing set)\n\ | |
82 | -d, --daemon Daemonize (do not wait for container to exit)\n\ | |
83 | -M, --remount Remount default fs inside container (/proc /dev/shm /dev/mqueue)\n\ | |
84 | ", | |
85 | .options = my_longopts, | |
86 | .parser = my_parser, | |
87 | .checker = NULL, | |
d67beb9e | 88 | .log_priority = "ERROR", |
89 | .log_file = "none", | |
727b9b16 | 90 | .daemonize = 0, |
727b9b16 | 91 | }; |
92 | ||
93 | static int my_parser(struct lxc_arguments *args, int c, char *arg) | |
94 | { | |
59e1663a | 95 | struct lxc_list *tmplist; |
96 | ||
727b9b16 | 97 | switch (c) { |
98 | case 's': | |
99 | args->flags = get_namespace_flags(arg); | |
100 | if (args->flags < 0) | |
101 | return -1; | |
102 | break; | |
103 | case 'u': | |
104 | if (!lookup_user(arg, &args->uid)) | |
105 | return -1; | |
106 | ||
107 | args->setuid = true; | |
108 | break; | |
109 | case 'H': | |
110 | args->want_hostname = arg; | |
111 | break; | |
112 | case 'i': | |
59e1663a | 113 | tmplist = malloc(sizeof(*tmplist)); |
114 | if (!tmplist) { | |
115 | SYSERROR("Failed to alloc lxc list"); | |
116 | free_ifname_list(); | |
727b9b16 | 117 | return -1; |
118 | } | |
119 | ||
59e1663a | 120 | lxc_list_add_elem(tmplist, arg); |
121 | lxc_list_add_tail(&ifnames, tmplist); | |
727b9b16 | 122 | break; |
123 | case 'd': | |
124 | args->daemonize = 1; | |
125 | break; | |
126 | case 'M': | |
127 | args->want_default_mounts = 1; | |
128 | break; | |
129 | } | |
130 | return 0; | |
131 | } | |
132 | ||
727b9b16 | 133 | static int get_namespace_flags(char *namespaces) |
c1bb25a8 | 134 | { |
727b9b16 | 135 | int flags = 0; |
c1bb25a8 | 136 | |
727b9b16 | 137 | if (lxc_namespace_2_std_identifiers(namespaces) < 0) |
138 | return -1; | |
139 | ||
140 | if (lxc_fill_namespace_flags(namespaces, &flags) < 0) | |
141 | return -1; | |
142 | ||
143 | return flags; | |
633cb02a | 144 | } |
145 | ||
a7547c5c | 146 | static bool lookup_user(const char *oparg, uid_t *uid) |
b7f85ccb | 147 | { |
3a5996ff | 148 | char name[PATH_MAX]; |
2dce415b DJ |
149 | struct passwd pwent; |
150 | struct passwd *pwentp = NULL; | |
151 | char *buf; | |
152 | size_t bufsize; | |
153 | int ret; | |
b7f85ccb | 154 | |
a7547c5c | 155 | if (!oparg || (oparg[0] == '\0')) |
31a1209d | 156 | return false; |
b7f85ccb | 157 | |
2dce415b DJ |
158 | bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); |
159 | if (bufsize == -1) | |
160 | bufsize = 1024; | |
161 | ||
162 | buf = malloc(bufsize); | |
163 | if (!buf) | |
164 | return false; | |
165 | ||
a7547c5c | 166 | if (sscanf(oparg, "%u", uid) < 1) { |
1615dc39 | 167 | /* not a uid -- perhaps a username */ |
a7547c5c | 168 | if (sscanf(oparg, "%s", name) < 1) { |
db8b325a | 169 | free(buf); |
31a1209d | 170 | return false; |
db8b325a | 171 | } |
1615dc39 | 172 | |
2dce415b DJ |
173 | ret = getpwnam_r(name, &pwent, buf, bufsize, &pwentp); |
174 | if (!pwentp) { | |
175 | if (ret == 0) | |
727b9b16 | 176 | SYSERROR("Could not find matched password record"); |
2dce415b | 177 | |
727b9b16 | 178 | SYSERROR("Invalid username \"%s\"", name); |
2dce415b | 179 | free(buf); |
31a1209d | 180 | return false; |
1615dc39 | 181 | } |
4f66541c | 182 | |
2dce415b | 183 | *uid = pwent.pw_uid; |
b7f85ccb | 184 | } else { |
2dce415b DJ |
185 | ret = getpwuid_r(*uid, &pwent, buf, bufsize, &pwentp); |
186 | if (!pwentp) { | |
187 | if (ret == 0) | |
727b9b16 | 188 | SYSERROR("Could not find matched password record"); |
2dce415b | 189 | |
727b9b16 | 190 | SYSERROR("Invalid uid : %u", *uid); |
2dce415b | 191 | free(buf); |
31a1209d | 192 | return false; |
b7f85ccb MH |
193 | } |
194 | } | |
2dce415b DJ |
195 | |
196 | free(buf); | |
31a1209d | 197 | return true; |
b7f85ccb MH |
198 | } |
199 | ||
4f66541c | 200 | static int mount_fs(const char *source, const char *target, const char *type) |
201 | { | |
202 | /* the umount may fail */ | |
203 | if (umount(target) < 0) | |
204 | ||
205 | if (mount(source, target, type, 0, NULL) < 0) | |
206 | return -1; | |
207 | ||
208 | return 0; | |
209 | } | |
210 | ||
211 | static void lxc_setup_fs(void) | |
212 | { | |
213 | (void)mount_fs("proc", "/proc", "proc"); | |
214 | ||
215 | /* if /dev has been populated by us, /dev/shm does not exist */ | |
216 | if (access("/dev/shm", F_OK)) | |
e581b9b5 | 217 | (void)mkdir("/dev/shm", 0770); |
4f66541c | 218 | |
219 | /* if we can't mount /dev/shm, continue anyway */ | |
220 | (void)mount_fs("shmfs", "/dev/shm", "tmpfs"); | |
221 | ||
222 | /* If we were able to mount /dev/shm, then /dev exists */ | |
223 | /* Sure, but it's read-only per config :) */ | |
224 | if (access("/dev/mqueue", F_OK)) | |
e581b9b5 | 225 | (void)mkdir("/dev/mqueue", 0660); |
4f66541c | 226 | |
227 | /* continue even without posix message queue support */ | |
228 | (void)mount_fs("mqueue", "/dev/mqueue", "mqueue"); | |
229 | } | |
230 | ||
50e98013 DL |
231 | static int do_start(void *arg) |
232 | { | |
344c9d81 MPS |
233 | int ret; |
234 | uint64_t wait_val; | |
50e98013 | 235 | struct start_arg *start_arg = arg; |
727b9b16 | 236 | char *const *args = start_arg->args; |
c1bb25a8 | 237 | const char *want_hostname = start_arg->want_hostname; |
344c9d81 MPS |
238 | |
239 | if (start_arg->setuid) { | |
240 | /* waiting until uid maps is set */ | |
7b6f89cd | 241 | ret = lxc_read_nointr(start_arg->wait_fd, &wait_val, sizeof(wait_val)); |
344c9d81 | 242 | if (ret == -1) { |
727b9b16 | 243 | SYSERROR("Failed to read eventfd"); |
244 | close(start_arg->wait_fd); | |
4f66541c | 245 | _exit(EXIT_FAILURE); |
344c9d81 MPS |
246 | } |
247 | } | |
c1bb25a8 | 248 | |
727b9b16 | 249 | if ((start_arg->flags & CLONE_NEWNS) && start_arg->want_default_mounts) |
c1bb25a8 SR |
250 | lxc_setup_fs(); |
251 | ||
727b9b16 | 252 | if ((start_arg->flags & CLONE_NEWUTS) && want_hostname) |
bed09c9c | 253 | if (sethostname(want_hostname, strlen(want_hostname)) < 0) { |
727b9b16 | 254 | SYSERROR("Failed to set hostname %s", want_hostname); |
4f66541c | 255 | _exit(EXIT_FAILURE); |
c1bb25a8 | 256 | } |
50e98013 | 257 | |
1a0e70ac | 258 | /* Setuid is useful even without a new user id space. */ |
727b9b16 | 259 | if (start_arg->setuid && setuid(start_arg->uid)) { |
260 | SYSERROR("Failed to set uid %d", start_arg->uid); | |
4f66541c | 261 | _exit(EXIT_FAILURE); |
50e98013 DL |
262 | } |
263 | ||
264 | execvp(args[0], args); | |
265 | ||
727b9b16 | 266 | SYSERROR("Failed to exec: '%s'", args[0]); |
50e98013 DL |
267 | return 1; |
268 | } | |
269 | ||
59e1663a | 270 | static void free_ifname_list(void) |
271 | { | |
272 | struct lxc_list *iterator, *next; | |
273 | ||
274 | lxc_list_for_each_safe (iterator, &ifnames, next) { | |
275 | lxc_list_del(iterator); | |
276 | free(iterator); | |
277 | } | |
278 | } | |
279 | ||
633cb02a | 280 | int main(int argc, char *argv[]) |
281 | { | |
a4255c08 | 282 | int ret; |
633cb02a | 283 | pid_t pid; |
727b9b16 | 284 | struct lxc_log log; |
285 | struct start_arg start_arg; | |
4f66541c | 286 | |
59e1663a | 287 | lxc_list_init(&ifnames); |
288 | ||
727b9b16 | 289 | if (lxc_caps_init()) |
290 | exit(EXIT_FAILURE); | |
633cb02a | 291 | |
727b9b16 | 292 | if (lxc_arguments_parse(&my_args, argc, argv)) |
b52b0595 | 293 | exit(EXIT_FAILURE); |
a11a544f | 294 | |
d67beb9e | 295 | log.name = my_args.name; |
296 | log.file = my_args.log_file; | |
297 | log.level = my_args.log_priority; | |
298 | log.prefix = my_args.progname; | |
299 | log.quiet = my_args.quiet; | |
300 | log.lxcpath = my_args.lxcpath[0]; | |
633cb02a | 301 | |
d67beb9e | 302 | if (lxc_log_init(&log)) { |
303 | free_ifname_list(); | |
304 | exit(EXIT_FAILURE); | |
727b9b16 | 305 | } |
c7029344 | 306 | |
35bfea7a | 307 | if (!*my_args.argv) { |
727b9b16 | 308 | ERROR("A command to execute in the new namespace is required"); |
59e1663a | 309 | free_ifname_list(); |
727b9b16 | 310 | exit(EXIT_FAILURE); |
311 | } | |
40625b63 | 312 | |
727b9b16 | 313 | if (my_args.flags == 0) { |
314 | ERROR("A namespace to execute command is required"); | |
59e1663a | 315 | free_ifname_list(); |
727b9b16 | 316 | exit(EXIT_FAILURE); |
317 | } | |
633cb02a | 318 | |
59e1663a | 319 | if (!(my_args.flags & CLONE_NEWNET) && lxc_list_len(&ifnames) > 0) { |
727b9b16 | 320 | ERROR("-i <interfacename> needs -s NETWORK option"); |
59e1663a | 321 | free_ifname_list(); |
b52b0595 | 322 | exit(EXIT_FAILURE); |
c1bb25a8 SR |
323 | } |
324 | ||
727b9b16 | 325 | if (!(my_args.flags & CLONE_NEWUTS) && my_args.want_hostname) { |
326 | ERROR("-H <hostname> needs -s UTSNAME option"); | |
59e1663a | 327 | free_ifname_list(); |
b52b0595 | 328 | exit(EXIT_FAILURE); |
c1bb25a8 SR |
329 | } |
330 | ||
727b9b16 | 331 | if (!(my_args.flags & CLONE_NEWNS) && my_args.want_default_mounts) { |
332 | ERROR("-M needs -s MOUNT option"); | |
59e1663a | 333 | free_ifname_list(); |
b52b0595 | 334 | exit(EXIT_FAILURE); |
633cb02a | 335 | } |
336 | ||
727b9b16 | 337 | if (my_args.setuid) { |
344c9d81 MPS |
338 | start_arg.wait_fd = eventfd(0, EFD_CLOEXEC); |
339 | if (start_arg.wait_fd < 0) { | |
727b9b16 | 340 | SYSERROR("Failed to create eventfd"); |
59e1663a | 341 | free_ifname_list(); |
344c9d81 MPS |
342 | exit(EXIT_FAILURE); |
343 | } | |
344 | } | |
345 | ||
727b9b16 | 346 | /* set start arguments for lxc_clone from lxc_arguments */ |
347 | start_arg.args = my_args.argv; | |
348 | start_arg.uid = my_args.uid; /* valid only if (flags & CLONE_NEWUSER) */ | |
349 | start_arg.setuid = my_args.setuid; | |
350 | start_arg.flags = my_args.flags; | |
351 | start_arg.want_hostname = my_args.want_hostname; | |
352 | start_arg.want_default_mounts = my_args.want_default_mounts; | |
727b9b16 | 353 | |
33258b95 | 354 | pid = lxc_clone(do_start, &start_arg, my_args.flags, NULL); |
50e98013 | 355 | if (pid < 0) { |
727b9b16 | 356 | ERROR("Failed to clone"); |
59e1663a | 357 | free_ifname_list(); |
b52b0595 | 358 | exit(EXIT_FAILURE); |
633cb02a | 359 | } |
360 | ||
727b9b16 | 361 | if (my_args.setuid) { |
362 | uint64_t wait_val = 1; | |
344c9d81 | 363 | /* enough space to accommodate uids */ |
57e2af15 | 364 | char umap[100]; |
344c9d81 MPS |
365 | |
366 | /* create new uid mapping using current UID and the one | |
367 | * specified as parameter | |
368 | */ | |
727b9b16 | 369 | ret = snprintf(umap, 100, "%d %d 1\n" , my_args.uid, getuid()); |
344c9d81 | 370 | if (ret < 0 || ret >= 100) { |
727b9b16 | 371 | ERROR("snprintf is failed"); |
59e1663a | 372 | free_ifname_list(); |
344c9d81 | 373 | close(start_arg.wait_fd); |
344c9d81 MPS |
374 | exit(EXIT_FAILURE); |
375 | } | |
376 | ||
23ccbded | 377 | ret = write_id_mapping(ID_TYPE_UID, pid, umap, strlen(umap)); |
344c9d81 | 378 | if (ret < 0) { |
727b9b16 | 379 | ERROR("Failed to map uid"); |
59e1663a | 380 | free_ifname_list(); |
344c9d81 | 381 | close(start_arg.wait_fd); |
344c9d81 MPS |
382 | exit(EXIT_FAILURE); |
383 | } | |
384 | ||
8367b31e | 385 | ret = lxc_write_nointr(start_arg.wait_fd, &wait_val, sizeof(wait_val)); |
344c9d81 | 386 | if (ret < 0) { |
727b9b16 | 387 | SYSERROR("Failed to write eventfd"); |
59e1663a | 388 | free_ifname_list(); |
344c9d81 | 389 | close(start_arg.wait_fd); |
344c9d81 MPS |
390 | exit(EXIT_FAILURE); |
391 | } | |
392 | } | |
393 | ||
59e1663a | 394 | if (lxc_list_len(&ifnames) > 0) { |
395 | struct lxc_list *iterator; | |
396 | char* ifname; | |
a7547c5c | 397 | pid_t lpid; |
59e1663a | 398 | |
399 | lxc_list_for_each(iterator, &ifnames) { | |
400 | ifname = iterator->elem; | |
401 | if (!ifname) | |
402 | continue; | |
d567a9a7 | 403 | |
a7547c5c CB |
404 | lpid = fork(); |
405 | if (lpid < 0) { | |
727b9b16 | 406 | SYSERROR("Failed to move network device \"%s\" to network namespace", |
59e1663a | 407 | ifname); |
727b9b16 | 408 | continue; |
409 | } | |
d567a9a7 | 410 | |
a7547c5c | 411 | if (lpid == 0) { |
d567a9a7 CB |
412 | char buf[256]; |
413 | ||
a7547c5c | 414 | ret = snprintf(buf, 256, "%d", lpid); |
d567a9a7 | 415 | if (ret < 0 || ret >= 256) |
4f66541c | 416 | _exit(EXIT_FAILURE); |
d567a9a7 | 417 | |
59e1663a | 418 | execlp("ip", "ip", "link", "set", "dev", ifname, "netns", buf, (char *)NULL); |
4f66541c | 419 | _exit(EXIT_FAILURE); |
d567a9a7 CB |
420 | } |
421 | ||
a7547c5c | 422 | if (wait_for_pid(lpid) != 0) |
727b9b16 | 423 | SYSERROR("Could not move interface \"%s\" into container %d", |
a7547c5c | 424 | ifname, lpid); |
c1bb25a8 | 425 | } |
59e1663a | 426 | |
427 | free_ifname_list(); | |
c1bb25a8 SR |
428 | } |
429 | ||
727b9b16 | 430 | if (my_args.daemonize) |
f0c6ee28 | 431 | exit(EXIT_SUCCESS); |
c1bb25a8 | 432 | |
d567a9a7 | 433 | if (wait_for_pid(pid) != 0) { |
727b9b16 | 434 | SYSERROR("Failed to wait for '%d'", pid); |
b52b0595 | 435 | exit(EXIT_FAILURE); |
633cb02a | 436 | } |
437 | ||
f3e65e7b | 438 | /* Call exit() directly on this function because it returns an exit code. */ |
d567a9a7 | 439 | exit(EXIT_SUCCESS); |
35bfea7a | 440 | } |