src/idmapped-mounts: Remove useless header file
[xfstests-dev.git] / src / idmapped-mounts / mount-idmapped.c
1 // SPDX-License-Identifier: GPL-2.0
2 #ifndef _GNU_SOURCE
3 #define _GNU_SOURCE
4 #endif
5
6 #include "../global.h"
7
8 #include <dirent.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <getopt.h>
12 #include <limits.h>
13 #include <linux/sched.h>
14 #include <sched.h>
15 #include <signal.h>
16 #include <stdbool.h>
17 #include <stdint.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <sys/stat.h>
22 #include <sys/syscall.h>
23 #include <sys/types.h>
24 #include <sys/wait.h>
25 #include <unistd.h>
26
27 #include "missing.h"
28 #include "utils.h"
29
30 /* A few helpful macros. */
31 #define STRLITERALLEN(x) (sizeof(""x"") - 1)
32
33 #define INTTYPE_TO_STRLEN(type)             \
34         (2 + (sizeof(type) <= 1             \
35                   ? 3                       \
36                   : sizeof(type) <= 2       \
37                         ? 5                 \
38                         : sizeof(type) <= 4 \
39                               ? 10          \
40                               : sizeof(type) <= 8 ? 20 : sizeof(int[-2 * (sizeof(type) > 8)])))
41
42 #define syserror(format, ...)                           \
43         ({                                              \
44                 fprintf(stderr, format, ##__VA_ARGS__); \
45                 (-errno);                               \
46         })
47
48 #define syserror_set(__ret__, format, ...)                    \
49         ({                                                    \
50                 typeof(__ret__) __internal_ret__ = (__ret__); \
51                 errno = labs(__ret__);                        \
52                 fprintf(stderr, format, ##__VA_ARGS__);       \
53                 __internal_ret__;                             \
54         })
55
56 struct list {
57         void *elem;
58         struct list *next;
59         struct list *prev;
60 };
61
62 #define list_for_each(__iterator, __list) \
63         for (__iterator = (__list)->next; __iterator != __list; __iterator = __iterator->next)
64
65 static inline void list_init(struct list *list)
66 {
67         list->elem = NULL;
68         list->next = list->prev = list;
69 }
70
71 static inline int list_empty(const struct list *list)
72 {
73         return list == list->next;
74 }
75
76 static inline void __list_add(struct list *new, struct list *prev, struct list *next)
77 {
78         next->prev = new;
79         new->next = next;
80         new->prev = prev;
81         prev->next = new;
82 }
83
84 static inline void list_add_tail(struct list *head, struct list *list)
85 {
86         __list_add(list, head->prev, head);
87 }
88
89 typedef enum idmap_type_t {
90         ID_TYPE_UID,
91         ID_TYPE_GID
92 } idmap_type_t;
93
94 struct id_map {
95         idmap_type_t map_type;
96         __u32 nsid;
97         __u32 hostid;
98         __u32 range;
99 };
100
101 static struct list active_map;
102
103 static int add_map_entry(__u32 id_host,
104                          __u32 id_ns,
105                          __u32 range,
106                          idmap_type_t map_type)
107 {
108         struct list *new_list = NULL;
109         struct id_map *newmap = NULL;
110
111         newmap = malloc(sizeof(*newmap));
112         if (!newmap)
113                 return -ENOMEM;
114
115         new_list = malloc(sizeof(struct list));
116         if (!new_list) {
117                 free(newmap);
118                 return -ENOMEM;
119         }
120
121         *newmap = (struct id_map){
122                 .hostid         = id_host,
123                 .nsid           = id_ns,
124                 .range          = range,
125                 .map_type       = map_type,
126         };
127
128         new_list->elem = newmap;
129         list_add_tail(&active_map, new_list);
130         return 0;
131 }
132
133 static int parse_map(char *map)
134 {
135         char types[2] = {'u', 'g'};
136         int ret, i;
137         __u32 id_host, id_ns, range;
138         char which;
139
140         if (!map)
141                 return -1;
142
143         ret = sscanf(map, "%c:%u:%u:%u", &which, &id_ns, &id_host, &range);
144         if (ret != 4)
145                 return -1;
146
147         if (which != 'b' && which != 'u' && which != 'g')
148                 return -1;
149
150         for (i = 0; i < 2; i++) {
151                 idmap_type_t map_type;
152
153                 if (which != types[i] && which != 'b')
154                         continue;
155
156                 if (types[i] == 'u')
157                         map_type = ID_TYPE_UID;
158                 else
159                         map_type = ID_TYPE_GID;
160
161                 ret = add_map_entry(id_host, id_ns, range, map_type);
162                 if (ret < 0)
163                         return ret;
164         }
165
166         return 0;
167 }
168
169 static int write_id_mapping(idmap_type_t map_type, pid_t pid, const char *buf, size_t buf_size)
170 {
171         int fd = -EBADF, setgroups_fd = -EBADF;
172         int fret = -1;
173         int ret;
174         char path[STRLITERALLEN("/proc/") + INTTYPE_TO_STRLEN(pid_t) +
175                   STRLITERALLEN("/setgroups") + 1];
176
177         if (geteuid() != 0 && map_type == ID_TYPE_GID) {
178                 ret = snprintf(path, sizeof(path), "/proc/%d/setgroups", pid);
179                 if (ret < 0 || ret >= sizeof(path))
180                         goto out;
181
182                 setgroups_fd = open(path, O_WRONLY | O_CLOEXEC);
183                 if (setgroups_fd < 0 && errno != ENOENT) {
184                         syserror("Failed to open \"%s\"", path);
185                         goto out;
186                 }
187
188                 if (setgroups_fd >= 0) {
189                         ret = write_nointr(setgroups_fd, "deny\n", STRLITERALLEN("deny\n"));
190                         if (ret != STRLITERALLEN("deny\n")) {
191                                 syserror("Failed to write \"deny\" to \"/proc/%d/setgroups\"", pid);
192                                 goto out;
193                         }
194                 }
195         }
196
197         ret = snprintf(path, sizeof(path), "/proc/%d/%cid_map", pid, map_type == ID_TYPE_UID ? 'u' : 'g');
198         if (ret < 0 || ret >= sizeof(path))
199                 goto out;
200
201         fd = open(path, O_WRONLY | O_CLOEXEC);
202         if (fd < 0) {
203                 syserror("Failed to open \"%s\"", path);
204                 goto out;
205         }
206
207         ret = write_nointr(fd, buf, buf_size);
208         if (ret != buf_size) {
209                 syserror("Failed to write %cid mapping to \"%s\"",
210                          map_type == ID_TYPE_UID ? 'u' : 'g', path);
211                 goto out;
212         }
213
214         fret = 0;
215 out:
216         if (fd >= 0)
217                 close(fd);
218         if (setgroups_fd >= 0)
219                 close(setgroups_fd);
220
221         return fret;
222 }
223
224 static int map_ids_from_idmap(struct list *idmap, pid_t pid)
225 {
226         int fill, left;
227         char mapbuf[4096] = {};
228         bool had_entry = false;
229         idmap_type_t map_type, u_or_g;
230
231         for (map_type = ID_TYPE_UID, u_or_g = 'u';
232              map_type <= ID_TYPE_GID; map_type++, u_or_g = 'g') {
233                 char *pos = mapbuf;
234                 int ret;
235                 struct list *iterator;
236
237
238                 list_for_each(iterator, idmap) {
239                         struct id_map *map = iterator->elem;
240                         if (map->map_type != map_type)
241                                 continue;
242
243                         had_entry = true;
244
245                         left = 4096 - (pos - mapbuf);
246                         fill = snprintf(pos, left, "%u %u %u\n", map->nsid, map->hostid, map->range);
247                         /*
248                          * The kernel only takes <= 4k for writes to
249                          * /proc/<pid>/{g,u}id_map
250                          */
251                         if (fill <= 0 || fill >= left)
252                                 return syserror_set(-E2BIG, "Too many %cid mappings defined", u_or_g);
253
254                         pos += fill;
255                 }
256                 if (!had_entry)
257                         continue;
258
259                 ret = write_id_mapping(map_type, pid, mapbuf, pos - mapbuf);
260                 if (ret < 0)
261                         return syserror("Failed to write mapping: %s", mapbuf);
262
263                 memset(mapbuf, 0, sizeof(mapbuf));
264         }
265
266         return 0;
267 }
268
269 static int get_userns_fd_from_idmap(struct list *idmap)
270 {
271         int ret;
272         pid_t pid;
273         char path_ns[STRLITERALLEN("/proc/") + INTTYPE_TO_STRLEN(pid_t) +
274                   STRLITERALLEN("/ns/user") + 1];
275
276         pid = do_clone(get_userns_fd_cb, NULL, CLONE_NEWUSER | CLONE_NEWNS);
277         if (pid < 0)
278                 return -errno;
279
280         ret = map_ids_from_idmap(idmap, pid);
281         if (ret < 0)
282                 return ret;
283
284         ret = snprintf(path_ns, sizeof(path_ns), "/proc/%d/ns/user", pid);
285         if (ret < 0 || (size_t)ret >= sizeof(path_ns))
286                 ret = -EIO;
287         else
288                 ret = open(path_ns, O_RDONLY | O_CLOEXEC | O_NOCTTY);
289
290         (void)kill(pid, SIGKILL);
291         (void)wait_for_pid(pid);
292         return ret;
293 }
294
295 static inline bool strnequal(const char *str, const char *eq, size_t len)
296 {
297         return strncmp(str, eq, len) == 0;
298 }
299
300 static void usage(void)
301 {
302         const char *text = "\
303 mount-idmapped --map-mount=<idmap> <source> <target>\n\
304 \n\
305 Create an idmapped mount of <source> at <target>\n\
306 Options:\n\
307   --map-mount=<idmap>\n\
308         Specify an idmap for the <target> mount in the format\n\
309         <idmap-type>:<id-from>:<id-to>:<id-range>\n\
310         The <idmap-type> can be:\n\
311         \"b\" or \"both\"       -> map both uids and gids\n\
312         \"u\" or \"uid\"        -> map uids\n\
313         \"g\" or \"gid\"        -> map gids\n\
314         For example, specifying:\n\
315         both:1000:1001:1        -> map uid and gid 1000 to uid and gid 1001 in <target> and no other ids\n\
316         uid:20000:100000:1000   -> map uid 20000 to uid 100000, uid 20001 to uid 100001 [...] in <target>\n\
317         Currently up to 340 separate idmappings may be specified.\n\n\
318   --map-mount=/proc/<pid>/ns/user\n\
319         Specify a path to a user namespace whose idmap is to be used.\n\n\
320   --recursive\n\
321         Copy the whole mount tree from <source> and apply the idmap to everyone at <target>.\n\n\
322 Examples:\n\
323   - Create an idmapped mount of /source on /target with both ('b') uids and gids mapped:\n\
324         mount-idmapped --map-mount b:0:10000:10000 /source /target\n\n\
325   - Create an idmapped mount of /source on /target with uids ('u') and gids ('g') mapped separately:\n\
326         mount-idmapped --map-mount u:0:10000:10000 g:0:20000:20000 /source /target\n\n\
327 ";
328         fprintf(stderr, "%s", text);
329         _exit(EXIT_SUCCESS);
330 }
331
332 #define exit_usage(format, ...)                         \
333         ({                                              \
334                 fprintf(stderr, format, ##__VA_ARGS__); \
335                 usage();                                \
336         })
337
338 #define exit_log(format, ...)                           \
339         ({                                              \
340                 fprintf(stderr, format, ##__VA_ARGS__); \
341                 exit(EXIT_FAILURE);                     \
342         })
343
344 static const struct option longopts[] = {
345         {"map-mount",   required_argument,      0,      'a'},
346         {"help",        no_argument,            0,      'c'},
347         {"recursive",   no_argument,            0,      'd'},
348         { NULL,         0,                      0,      0  },
349 };
350
351 int main(int argc, char *argv[])
352 {
353         int fd_userns = -EBADF;
354         int index = 0;
355         const char *source = NULL, *target = NULL;
356         bool recursive = false;
357         int fd_tree, new_argc, ret;
358         char *const *new_argv;
359
360         list_init(&active_map);
361         while ((ret = getopt_long_only(argc, argv, "", longopts, &index)) != -1) {
362                 switch (ret) {
363                 case 'a':
364                         if (strnequal(optarg, "/proc/", STRLITERALLEN("/proc/"))) {
365                                 fd_userns = open(optarg, O_RDONLY | O_CLOEXEC);
366                                 if (fd_userns < 0)
367                                         exit_log("%m - Failed top open user namespace path %s\n", optarg);
368                                 break;
369                         }
370
371                         ret = parse_map(optarg);
372                         if (ret < 0)
373                                 exit_log("Failed to parse idmaps for mount\n");
374                         break;
375                 case 'd':
376                         recursive = true;
377                         break;
378                 case 'c':
379                         /* fallthrough */
380                 default:
381                         usage();
382                 }
383         }
384
385         new_argv = &argv[optind];
386         new_argc = argc - optind;
387         if (new_argc < 2)
388                 exit_usage("Missing source or target mountpoint\n\n");
389         source = new_argv[0];
390         target = new_argv[1];
391
392         fd_tree = sys_open_tree(-EBADF, source,
393                                 OPEN_TREE_CLONE |
394                                 OPEN_TREE_CLOEXEC |
395                                 AT_EMPTY_PATH |
396                                 (recursive ? AT_RECURSIVE : 0));
397         if (fd_tree < 0) {
398                 exit_log("%m - Failed to open %s\n", source);
399                 exit(EXIT_FAILURE);
400         }
401
402         if (!list_empty(&active_map) || fd_userns >= 0) {
403                 struct mount_attr attr = {
404                         .attr_set = MOUNT_ATTR_IDMAP,
405                 };
406
407                 if (fd_userns >= 0)
408                         attr.userns_fd = fd_userns;
409                 else
410                         attr.userns_fd = get_userns_fd_from_idmap(&active_map);
411                 if (attr.userns_fd < 0)
412                         exit_log("%m - Failed to create user namespace\n");
413
414                 ret = sys_mount_setattr(fd_tree, "", AT_EMPTY_PATH | AT_RECURSIVE,
415                                         &attr, sizeof(attr));
416                 if (ret < 0)
417                         exit_log("%m - Failed to change mount attributes\n");
418                 close(attr.userns_fd);
419         }
420
421         ret = sys_move_mount(fd_tree, "", -EBADF, target,
422                              MOVE_MOUNT_F_EMPTY_PATH);
423         if (ret < 0)
424                 exit_log("%m - Failed to attach mount to %s\n", target);
425         close(fd_tree);
426
427         exit(EXIT_SUCCESS);
428 }