1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3 * This file and its contents are supplied under the terms of the
4 * Common Development and Distribution License ("CDDL"), version 1.0.
5 * You may only use this file in accordance with the terms of version
6 * 1.0 of the CDDL.
7 *
8 * A full copy of the text of the CDDL should have accompanied this
9 * source. A copy of the CDDL is also available via the Internet at
10 * https://opensource.org/license/CDDL-1.0.
11 */
12
13 #ifndef _GNU_SOURCE
14 #define _GNU_SOURCE
15 #endif
16
17 #include <stdio.h>
18 #include <stdlib.h>
19 #include <stdbool.h>
20 #include <stddef.h>
21 #include <string.h>
22 #include <linux/types.h>
23 #include <sys/wait.h>
24 #include <sys/stat.h>
25 #include <sys/mount.h>
26 #include <fcntl.h>
27 #include <errno.h>
28 #include <sched.h>
29 #include <syscall.h>
30 #include <sys/socket.h>
31
32 #include <sys/list.h>
33
34 #ifndef UINT_MAX
35 #define UINT_MAX 4294967295U
36 #endif
37
38 #ifndef __NR_Linux
39 #if defined __alpha__
40 #define __NR_Linux 110
41 #elif defined _MIPS_SIM
42 #if _MIPS_SIM == _MIPS_SIM_ABI32
43 #define __NR_Linux 4000
44 #endif
45 #if _MIPS_SIM == _MIPS_SIM_NABI32
46 #define __NR_Linux 6000
47 #endif
48 #if _MIPS_SIM == _MIPS_SIM_ABI64
49 #define __NR_Linux 5000
50 #endif
51 #elif defined __ia64__
52 #define __NR_Linux 1024
53 #else
54 #define __NR_Linux 0
55 #endif
56 #endif
57
58 #ifndef __NR_mount_setattr
59 #define __NR_mount_setattr (442 + __NR_Linux)
60 #endif
61
62 #ifndef __NR_open_tree
63 #define __NR_open_tree (428 + __NR_Linux)
64 #endif
65
66 #ifndef __NR_move_mount
67 #define __NR_move_mount (429 + __NR_Linux)
68 #endif
69
70 #ifndef MNT_DETACH
71 #define MNT_DETACH 2
72 #endif
73
74 #ifndef MOVE_MOUNT_F_EMPTY_PATH
75 #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004
76 #endif
77
78 #ifndef MOUNT_ATTR_IDMAP
79 #define MOUNT_ATTR_IDMAP 0x00100000
80 #endif
81
82 #ifndef OPEN_TREE_CLONE
83 #define OPEN_TREE_CLONE 1
84 #endif
85
86 #ifndef OPEN_TREE_CLOEXEC
87 #define OPEN_TREE_CLOEXEC O_CLOEXEC
88 #endif
89
90 #ifndef AT_RECURSIVE
91 #define AT_RECURSIVE 0x8000
92 #endif
93
94 typedef struct {
95 __u64 attr_set;
96 __u64 attr_clr;
97 __u64 propagation;
98 __u64 userns_fd;
99 } mount_attr_t;
100
101 static inline int
sys_mount_setattr(int dfd,const char * path,unsigned int flags,mount_attr_t * attr,size_t size)102 sys_mount_setattr(int dfd, const char *path, unsigned int flags,
103 mount_attr_t *attr, size_t size)
104 {
105 return (syscall(__NR_mount_setattr, dfd, path, flags, attr, size));
106 }
107
108 static inline int
sys_open_tree(int dfd,const char * filename,unsigned int flags)109 sys_open_tree(int dfd, const char *filename, unsigned int flags)
110 {
111 return (syscall(__NR_open_tree, dfd, filename, flags));
112 }
113
sys_move_mount(int from_dfd,const char * from_pathname,int to_dfd,const char * to_pathname,unsigned int flags)114 static inline int sys_move_mount(int from_dfd, const char *from_pathname,
115 int to_dfd, const char *to_pathname, unsigned int flags)
116 {
117 return (syscall(__NR_move_mount, from_dfd, from_pathname, to_dfd,
118 to_pathname, flags));
119 }
120
121 typedef enum idmap_type_t {
122 TYPE_UID,
123 TYPE_GID,
124 TYPE_BOTH
125 } idmap_type_t;
126
127 struct idmap_entry {
128 __u32 first;
129 __u32 lower_first;
130 __u32 count;
131 idmap_type_t type;
132 list_node_t node;
133 };
134
135 static void
log_msg(const char * msg,...)136 log_msg(const char *msg, ...)
137 {
138 va_list ap;
139
140 va_start(ap, msg);
141 vfprintf(stderr, msg, ap);
142 fputc('\n', stderr);
143 va_end(ap);
144 }
145
146 #define log_errno(msg, args...) \
147 do { \
148 log_msg("%s:%d:%s: [%m] " msg, __FILE__, __LINE__,\
149 __FUNCTION__, ##args); \
150 } while (0)
151
152 /*
153 * Parse the idmapping in the following format
154 * and add to the list:
155 *
156 * u:nsid_first:hostid_first:count
157 * g:nsid_first:hostid_first:count
158 * b:nsid_first:hostid_first:count
159 *
160 * The delimiter can be : or space character.
161 *
162 * Return:
163 * 0 if success
164 * ENOMEM if out of memory
165 * EINVAL if wrong arg or input
166 */
167 static int
parse_idmap_entry(list_t * head,char * input)168 parse_idmap_entry(list_t *head, char *input)
169 {
170 char *token, *savedptr = NULL;
171 struct idmap_entry *entry;
172 unsigned long ul;
173 char *delimiter = (char *)": ";
174 char c;
175
176 if (!input || !head)
177 return (EINVAL);
178 entry = malloc(sizeof (*entry));
179 if (!entry)
180 return (ENOMEM);
181
182 token = strtok_r(input, delimiter, &savedptr);
183 if (token)
184 c = token[0];
185 if (!token || (c != 'b' && c != 'u' && c != 'g'))
186 goto errout;
187 entry->type = (c == 'b') ? TYPE_BOTH :
188 ((c == 'u') ? TYPE_UID : TYPE_GID);
189
190 token = strtok_r(NULL, delimiter, &savedptr);
191 if (!token)
192 goto errout;
193 ul = strtoul(token, NULL, 10);
194 if (ul > UINT_MAX || errno != 0)
195 goto errout;
196 entry->first = (__u32)ul;
197
198 token = strtok_r(NULL, delimiter, &savedptr);
199 if (!token)
200 goto errout;
201 ul = strtoul(token, NULL, 10);
202 if (ul > UINT_MAX || errno != 0)
203 goto errout;
204 entry->lower_first = (__u32)ul;
205
206 token = strtok_r(NULL, delimiter, &savedptr);
207 if (!token)
208 goto errout;
209 ul = strtoul(token, NULL, 10);
210 if (ul > UINT_MAX || errno != 0)
211 goto errout;
212 entry->count = (__u32)ul;
213
214 list_insert_tail(head, entry);
215
216 return (0);
217
218 errout:
219 free(entry);
220 return (EINVAL);
221 }
222
223 /*
224 * Release all the entries in the list
225 */
226 static void
free_idmap(list_t * head)227 free_idmap(list_t *head)
228 {
229 struct idmap_entry *entry;
230
231 while ((entry = list_remove_head(head)) != NULL)
232 free(entry);
233 /* list_destroy() to be done by the caller */
234 }
235
236 /*
237 * Write all bytes in the buffer to fd
238 */
239 static ssize_t
write_buf(int fd,const char * buf,size_t buf_size)240 write_buf(int fd, const char *buf, size_t buf_size)
241 {
242 ssize_t written, total_written = 0;
243 size_t remaining = buf_size;
244 char *position = (char *)buf;
245
246 for (;;) {
247 written = write(fd, position, remaining);
248 if (written < 0 && errno == EINTR)
249 continue;
250 if (written < 0) {
251 log_errno("write");
252 return (written);
253 }
254 total_written += written;
255 if (total_written == buf_size)
256 break;
257 remaining -= written;
258 position += written;
259 }
260
261 return (total_written);
262 }
263
264 /*
265 * Read data from file into buffer
266 */
267 static ssize_t
read_buf(int fd,char * buf,size_t buf_size)268 read_buf(int fd, char *buf, size_t buf_size)
269 {
270 int ret;
271 for (;;) {
272 ret = read(fd, buf, buf_size);
273 if (ret < 0 && errno == EINTR)
274 continue;
275 break;
276 }
277 if (ret < 0)
278 log_errno("read");
279 return (ret);
280 }
281
282 /*
283 * Write idmap of the given type in the buffer to the
284 * process' uid_map or gid_map proc file.
285 *
286 * Return:
287 * 0 if success
288 * errno if there's any error
289 */
290 static int
write_idmap(pid_t pid,char * buf,size_t buf_size,idmap_type_t type)291 write_idmap(pid_t pid, char *buf, size_t buf_size, idmap_type_t type)
292 {
293 char path[PATH_MAX];
294 int fd;
295 int ret;
296
297 (void) snprintf(path, sizeof (path), "/proc/%d/%cid_map",
298 pid, type == TYPE_UID ? 'u' : 'g');
299 fd = open(path, O_WRONLY | O_CLOEXEC);
300 if (fd < 0) {
301 ret = errno;
302 log_errno("open(%s)", path);
303 goto out;
304 }
305 ret = write_buf(fd, buf, buf_size);
306 if (ret < 0)
307 ret = errno;
308 else
309 ret = 0;
310 out:
311 if (fd >= 0)
312 close(fd);
313 return (ret);
314 }
315
316 /*
317 * Write idmap info in the list to the process
318 * user namespace, i.e. its /proc/<pid>/uid_map
319 * and /proc/<pid>/gid_map file.
320 *
321 * Return:
322 * 0 if success
323 * errno if it fails
324 */
325 static int
write_pid_idmaps(pid_t pid,list_t * head)326 write_pid_idmaps(pid_t pid, list_t *head)
327 {
328 char *buf_uids, *buf_gids;
329 char *curr_bufu, *curr_bufg;
330 /* max 4k to be allowed for each map */
331 int size_buf_uids = 4096, size_buf_gids = 4096;
332 struct idmap_entry *entry;
333 int uid_filled, gid_filled;
334 int ret = 0;
335 int has_uids = 0, has_gids = 0;
336 size_t buf_size;
337
338 buf_uids = malloc(size_buf_uids);
339 if (!buf_uids)
340 return (ENOMEM);
341 buf_gids = malloc(size_buf_gids);
342 if (!buf_gids) {
343 free(buf_uids);
344 return (ENOMEM);
345 }
346 curr_bufu = buf_uids;
347 curr_bufg = buf_gids;
348
349 for (entry = list_head(head); entry; entry = list_next(head, entry)) {
350 if (entry->type == TYPE_UID || entry->type == TYPE_BOTH) {
351 uid_filled = snprintf(curr_bufu, size_buf_uids,
352 "%u %u %u\n", entry->first, entry->lower_first,
353 entry->count);
354 if (uid_filled <= 0 || uid_filled >= size_buf_uids) {
355 ret = E2BIG;
356 goto out;
357 }
358 curr_bufu += uid_filled;
359 size_buf_uids -= uid_filled;
360 has_uids = 1;
361 }
362 if (entry->type == TYPE_GID || entry->type == TYPE_BOTH) {
363 gid_filled = snprintf(curr_bufg, size_buf_gids,
364 "%u %u %u\n", entry->first, entry->lower_first,
365 entry->count);
366 if (gid_filled <= 0 || gid_filled >= size_buf_gids) {
367 ret = E2BIG;
368 goto out;
369 }
370 curr_bufg += gid_filled;
371 size_buf_gids -= gid_filled;
372 has_gids = 1;
373 }
374 }
375 if (has_uids) {
376 buf_size = curr_bufu - buf_uids;
377 ret = write_idmap(pid, buf_uids, buf_size, TYPE_UID);
378 if (ret)
379 goto out;
380 }
381 if (has_gids) {
382 buf_size = curr_bufg - buf_gids;
383 ret = write_idmap(pid, buf_gids, buf_size, TYPE_GID);
384 }
385
386 out:
387 free(buf_uids);
388 free(buf_gids);
389 return (ret);
390 }
391
392 /*
393 * Wait for the child process to exit
394 * and reap it.
395 *
396 * Return:
397 * process exit code if available
398 */
399 static int
wait_for_pid(pid_t pid)400 wait_for_pid(pid_t pid)
401 {
402 int status;
403 int ret;
404
405 for (;;) {
406 ret = waitpid(pid, &status, 0);
407 if (ret < 0) {
408 if (errno == EINTR)
409 continue;
410 return (EXIT_FAILURE);
411 }
412 break;
413 }
414 if (!WIFEXITED(status))
415 return (EXIT_FAILURE);
416 return (WEXITSTATUS(status));
417 }
418
419 /*
420 * Get the file descriptor of the process user namespace
421 * given its pid.
422 *
423 * Return:
424 * fd if success
425 * -1 if it fails
426 */
427 static int
userns_fd_from_pid(pid_t pid)428 userns_fd_from_pid(pid_t pid)
429 {
430 int fd;
431 char path[PATH_MAX];
432
433 (void) snprintf(path, sizeof (path), "/proc/%d/ns/user", pid);
434 fd = open(path, O_RDONLY | O_CLOEXEC);
435 if (fd < 0)
436 log_errno("open(%s)", path);
437 return (fd);
438 }
439
440 /*
441 * Get the user namespace file descriptor given a list
442 * of idmap info.
443 *
444 * Return:
445 * fd if success
446 * -errno if it fails
447 */
448 static int
userns_fd_from_idmap(list_t * head)449 userns_fd_from_idmap(list_t *head)
450 {
451 pid_t pid;
452 int ret, fd;
453 int fds[2];
454 char c;
455 int saved_errno = 0;
456
457 /* socketpair for bidirectional communication */
458 ret = socketpair(AF_LOCAL, SOCK_STREAM | SOCK_CLOEXEC, 0, fds);
459 if (ret) {
460 log_errno("socketpair");
461 return (-errno);
462 }
463
464 pid = fork();
465 if (pid < 0) {
466 log_errno("fork");
467 fd = -errno;
468 goto out;
469 }
470
471 if (pid == 0) {
472 /* child process */
473 ret = unshare(CLONE_NEWUSER);
474 if (ret == 0) {
475 /* notify the parent of success */
476 ret = write_buf(fds[1], "1", 1);
477 if (ret < 0)
478 saved_errno = errno;
479 else {
480 /*
481 * Until the parent has written to idmap,
482 * we cannot exit, otherwise the defunct
483 * process is owned by the real root, writing
484 * to its idmap ends up with EPERM in the
485 * context of a user ns
486 */
487 ret = read_buf(fds[1], &c, 1);
488 if (ret < 0)
489 saved_errno = errno;
490 }
491 } else {
492 saved_errno = errno;
493 log_errno("unshare");
494 ret = write_buf(fds[1], "0", 1);
495 if (ret < 0)
496 saved_errno = errno;
497 }
498 exit(saved_errno);
499 }
500
501 /* parent process */
502 ret = read_buf(fds[0], &c, 1);
503 if (ret == 1 && c == '1') {
504 ret = write_pid_idmaps(pid, head);
505 if (!ret) {
506 fd = userns_fd_from_pid(pid);
507 if (fd < 0)
508 fd = -errno;
509 } else {
510 fd = -ret;
511 }
512 /* Let child know it can exit */
513 (void) write_buf(fds[0], "1", 1);
514 } else {
515 fd = -EBADF;
516 }
517 (void) wait_for_pid(pid);
518 out:
519 close(fds[0]);
520 close(fds[1]);
521 return (fd);
522 }
523
524 /*
525 * Check if the operating system supports idmapped mount on the
526 * given path or not.
527 *
528 * Return:
529 * true if supported
530 * false if not supported
531 */
532 static bool
is_idmap_supported(char * path)533 is_idmap_supported(char *path)
534 {
535 list_t head;
536 int ret;
537 int tree_fd = -EBADF, path_fd = -EBADF;
538 mount_attr_t attr = {
539 .attr_set = MOUNT_ATTR_IDMAP,
540 .userns_fd = -EBADF,
541 };
542
543 /* strtok_r() won't be happy with a const string */
544 /* To check if idmapped mount can be done in a user ns, map 0 to 0 */
545 char *input = strdup("b:0:0:1");
546
547 if (!input) {
548 errno = ENOMEM;
549 log_errno("strdup");
550 return (false);
551 }
552
553 list_create(&head, sizeof (struct idmap_entry),
554 offsetof(struct idmap_entry, node));
555 ret = parse_idmap_entry(&head, input);
556 if (ret) {
557 errno = ret;
558 log_errno("parse_idmap_entry(%s)", input);
559 goto out1;
560 }
561 ret = userns_fd_from_idmap(&head);
562 if (ret < 0)
563 goto out1;
564 attr.userns_fd = ret;
565 ret = openat(-EBADF, path, O_DIRECTORY | O_CLOEXEC);
566 if (ret < 0) {
567 log_errno("openat(%s)", path);
568 goto out;
569 }
570 path_fd = ret;
571 ret = sys_open_tree(path_fd, "", AT_EMPTY_PATH | AT_NO_AUTOMOUNT |
572 AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE);
573 if (ret < 0) {
574 log_errno("sys_open_tree");
575 goto out;
576 }
577 tree_fd = ret;
578 ret = sys_mount_setattr(tree_fd, "", AT_EMPTY_PATH, &attr,
579 sizeof (attr));
580 if (ret < 0) {
581 log_errno("sys_mount_setattr");
582 }
583 out:
584 close(attr.userns_fd);
585 out1:
586 free_idmap(&head);
587 list_destroy(&head);
588 if (tree_fd >= 0)
589 close(tree_fd);
590 if (path_fd >= 0)
591 close(path_fd);
592 free(input);
593 return (ret == 0);
594 }
595
596 /*
597 * Check if the given path is a mount point or not.
598 *
599 * Return:
600 * true if it is
601 * false otherwise
602 */
603 static bool
is_mountpoint(char * path)604 is_mountpoint(char *path)
605 {
606 char *parent;
607 struct stat st_me, st_parent;
608 bool ret;
609
610 parent = malloc(strlen(path)+4);
611 if (!parent) {
612 errno = ENOMEM;
613 log_errno("malloc");
614 return (false);
615 }
616 strcat(strcpy(parent, path), "/..");
617 if (lstat(path, &st_me) != 0 ||
618 lstat(parent, &st_parent) != 0)
619 ret = false;
620 else
621 if (st_me.st_dev != st_parent.st_dev ||
622 st_me.st_ino == st_parent.st_ino)
623 ret = true;
624 else
625 ret = false;
626 free(parent);
627 return (ret);
628 }
629
630 /*
631 * Remount the source on the new target folder with the given
632 * list of idmap info. If target is NULL, the source will be
633 * unmounted and then remounted if it is a mountpoint, otherwise
634 * no unmount is done, the source is simply idmap remounted.
635 *
636 * Return:
637 * 0 if success
638 * -errno otherwise
639 */
640 static int
do_idmap_mount(list_t * idmap,char * source,char * target,int flags)641 do_idmap_mount(list_t *idmap, char *source, char *target, int flags)
642 {
643 int ret;
644 int tree_fd = -EBADF, source_fd = -EBADF;
645 mount_attr_t attr = {
646 .attr_set = MOUNT_ATTR_IDMAP,
647 .userns_fd = -EBADF,
648 };
649
650 ret = userns_fd_from_idmap(idmap);
651 if (ret < 0)
652 goto out1;
653 attr.userns_fd = ret;
654 ret = openat(-EBADF, source, O_DIRECTORY | O_CLOEXEC);
655 if (ret < 0) {
656 ret = -errno;
657 log_errno("openat(%s)", source);
658 goto out;
659 }
660 source_fd = ret;
661 ret = sys_open_tree(source_fd, "", AT_EMPTY_PATH | AT_NO_AUTOMOUNT |
662 AT_SYMLINK_NOFOLLOW | OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE | flags);
663 if (ret < 0) {
664 ret = -errno;
665 log_errno("sys_open_tree");
666 goto out;
667 }
668 tree_fd = ret;
669 ret = sys_mount_setattr(tree_fd, "", AT_EMPTY_PATH | flags, &attr,
670 sizeof (attr));
671 if (ret < 0) {
672 ret = -errno;
673 log_errno("sys_mount_setattr");
674 goto out;
675 }
676 if (target == NULL && is_mountpoint(source)) {
677 ret = umount2(source, MNT_DETACH);
678 if (ret < 0) {
679 ret = -errno;
680 log_errno("umount2(%s)", source);
681 goto out;
682 }
683 }
684 ret = sys_move_mount(tree_fd, "", -EBADF, target == NULL ?
685 source : target, MOVE_MOUNT_F_EMPTY_PATH);
686 if (ret < 0) {
687 ret = -errno;
688 log_errno("sys_move_mount(%s)", target == NULL ?
689 source : target);
690 }
691 out:
692 close(attr.userns_fd);
693 out1:
694 if (tree_fd >= 0)
695 close(tree_fd);
696 if (source_fd >= 0)
697 close(source_fd);
698 return (ret);
699 }
700
701 static void
print_usage(char * argv[])702 print_usage(char *argv[])
703 {
704 fprintf(stderr, "Usage: %s [-r] [-c] [-m <idmap1>] [-m <idmap2>]" \
705 " ... [<source>] [<target>]\n", argv[0]);
706 fprintf(stderr, "\n");
707 fprintf(stderr, " -r Recursively do idmapped mount.\n");
708 fprintf(stderr, "\n");
709 fprintf(stderr, " -c Checks if idmapped mount is supported " \
710 "on the <source> by the operating system or not.\n");
711 fprintf(stderr, "\n");
712 fprintf(stderr, " -m <idmap> to specify the idmap info, " \
713 "in the following format:\n");
714 fprintf(stderr, " <id_type>:<nsid_first>:<hostid_first>:<count>\n");
715 fprintf(stderr, "\n");
716 fprintf(stderr, " <id_type> can be either of 'b', 'u', and 'g'.\n");
717 fprintf(stderr, "\n");
718 fprintf(stderr, "The <source> folder will be mounted at <target> " \
719 "with the provided idmap information.\nIf no <target> is " \
720 "specified, and <source> is a mount point, " \
721 "then <source> will be unmounted and then remounted.\n");
722 }
723
724 int
main(int argc,char * argv[])725 main(int argc, char *argv[])
726 {
727 int opt;
728 list_t idmap_head;
729 int check_supported = 0;
730 int ret = EXIT_SUCCESS;
731 char *source = NULL, *target = NULL;
732 int flags = 0;
733
734 list_create(&idmap_head, sizeof (struct idmap_entry),
735 offsetof(struct idmap_entry, node));
736
737 while ((opt = getopt(argc, argv, "rcm:")) != -1) {
738 switch (opt) {
739 case 'r':
740 flags |= AT_RECURSIVE;
741 break;
742 case 'c':
743 check_supported = 1;
744 break;
745 case 'm':
746 ret = parse_idmap_entry(&idmap_head, optarg);
747 if (ret) {
748 errno = ret;
749 log_errno("parse_idmap_entry(%s)", optarg);
750 ret = EXIT_FAILURE;
751 goto out;
752 }
753 break;
754 default:
755 print_usage(argv);
756 exit(EXIT_FAILURE);
757 }
758 }
759
760 if (check_supported == 0 && list_is_empty(&idmap_head)) {
761 print_usage(argv);
762 ret = EXIT_FAILURE;
763 goto out;
764 }
765
766 if (optind >= argc) {
767 fprintf(stderr, "Expected to have <source>, <target>.\n");
768 print_usage(argv);
769 ret = EXIT_FAILURE;
770 goto out;
771 }
772
773 source = argv[optind];
774 if (optind < (argc - 1)) {
775 target = argv[optind + 1];
776 }
777
778 if (check_supported) {
779 free_idmap(&idmap_head);
780 list_destroy(&idmap_head);
781 if (is_idmap_supported(source)) {
782 printf("idmapped mount is supported on [%s].\n",
783 source);
784 return (EXIT_SUCCESS);
785 } else {
786 printf("idmapped mount is NOT supported.\n");
787 return (EXIT_FAILURE);
788 }
789 }
790
791 ret = do_idmap_mount(&idmap_head, source, target, flags);
792 if (ret)
793 ret = EXIT_FAILURE;
794 out:
795 free_idmap(&idmap_head);
796 list_destroy(&idmap_head);
797
798 exit(ret);
799 }
800