jail: cgroups: replace wrongly used assert()
[project/procd.git] / jail / fs.c
1 /*
2 * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
3 * Copyright (C) 2015 Etienne Champetier <champetier.etienne@gmail.com>
4 * Copyright (C) 2020 Daniel Golle <daniel@makrotopia.org>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU Lesser General Public License version 2.1
8 * as published by the Free Software Foundation
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 */
15
16 #define _GNU_SOURCE
17
18 #include <assert.h>
19 #include <elf.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <linux/limits.h>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <sys/mman.h>
28 #include <unistd.h>
29 #include <libgen.h>
30
31 #include <libubox/avl.h>
32 #include <libubox/avl-cmp.h>
33 #include <libubox/blobmsg.h>
34 #include <libubox/list.h>
35 #include <libubox/utils.h>
36
37 #include "elf.h"
38 #include "fs.h"
39 #include "jail.h"
40 #include "log.h"
41
42 #define UJAIL_NOAFILE "/tmp/.ujailnoafile"
43
44 struct mount {
45 struct avl_node avl;
46 const char *source;
47 const char *target;
48 const char *filesystemtype;
49 unsigned long mountflags;
50 unsigned long propflags;
51 const char *optstr;
52 int error;
53 bool inner;
54 };
55
56 struct avl_tree mounts;
57
58 static int do_mount(const char *root, const char *orig_source, const char *target, const char *filesystemtype,
59 unsigned long orig_mountflags, unsigned long propflags, const char *optstr, int error, bool inner)
60 {
61 struct stat s;
62 char new[PATH_MAX];
63 char *source = (char *)orig_source;
64 int fd;
65 bool is_bind = (orig_mountflags & MS_BIND);
66 bool is_mask = (source == (void *)(-1));
67 unsigned long mountflags = orig_mountflags;
68
69 if (source && is_bind && stat(source, &s)) {
70 ERROR("stat(%s) failed: %m\n", source);
71 return error;
72 }
73
74 if (!is_mask && orig_source && inner) {
75 if (asprintf(&source, "%s%s", root, orig_source) < 0)
76 return ENOMEM;
77 }
78
79 snprintf(new, sizeof(new), "%s%s", root, target?target:source);
80
81 if (is_mask) {
82 if (stat(new, &s))
83 return 0; /* doesn't exists, nothing to mask */
84
85 if (S_ISDIR(s.st_mode)) {/* use empty 0-sized tmpfs for directories */
86 if (mount(NULL, new, "tmpfs", MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_NOATIME, "size=0,mode=000"))
87 return error;
88 } else {
89 /* mount-bind 0-sized file having mode 000 */
90 if (mount(UJAIL_NOAFILE, new, "bind", MS_BIND, NULL))
91 return error;
92
93 if (mount(UJAIL_NOAFILE, new, "bind", MS_REMOUNT | MS_BIND | MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_NOATIME, NULL))
94 return error;
95 }
96
97 DEBUG("masked path %s\n", new);
98 return 0;
99 }
100
101
102 if (!is_bind || (source && S_ISDIR(s.st_mode))) {
103 mkdir_p(new, 0755);
104 } else if (is_bind && source) {
105 mkdir_p(dirname(new), 0755);
106 snprintf(new, sizeof(new), "%s%s", root, target?target:source);
107 fd = creat(new, 0644);
108 if (fd == -1) {
109 ERROR("creat(%s) failed: %m\n", new);
110 return error;
111 }
112 close(fd);
113 }
114
115 if (is_bind) {
116 if (mount(source?:new, new, filesystemtype?:"bind", MS_BIND | (mountflags & MS_REC), optstr)) {
117 if (error)
118 ERROR("failed to mount -B %s %s: %m\n", source, new);
119
120 if (inner)
121 free(source);
122
123 return error;
124 }
125 mountflags |= MS_REMOUNT;
126 }
127
128 const char *hack_fstype = ((!filesystemtype || strcmp(filesystemtype, "cgroup"))?filesystemtype:"cgroup2");
129 if (mount(source?:(is_bind?new:NULL), new, hack_fstype?:"none", mountflags, optstr)) {
130 if (error)
131 ERROR("failed to mount %s %s: %m\n", source, new);
132
133 if (inner)
134 free(source);
135
136 return error;
137 }
138
139 DEBUG("mount %s%s %s (%s)\n", (mountflags & MS_BIND)?"-B ":"", source, new,
140 (mountflags & MS_RDONLY)?"ro":"rw");
141
142 if (propflags && mount("none", new, "none", propflags, NULL)) {
143 if (error)
144 ERROR("failed to mount --make-... %s \n", new);
145
146 if (inner)
147 free(source);
148
149 return error;
150 }
151
152 if (inner)
153 free(source);
154
155 return 0;
156 }
157
158 static int _add_mount(const char *source, const char *target, const char *filesystemtype,
159 unsigned long mountflags, unsigned long propflags, const char *optstr,
160 int error, bool inner)
161 {
162 assert(target != NULL);
163
164 if (avl_find(&mounts, target))
165 return 1;
166
167 struct mount *m;
168 m = calloc(1, sizeof(struct mount));
169 if (!m)
170 return ENOMEM;
171
172 m->avl.key = m->target = strdup(target);
173 if (source) {
174 if (source != (void*)(-1))
175 m->source = strdup(source);
176 else
177 m->source = (void*)(-1);
178 }
179 if (filesystemtype)
180 m->filesystemtype = strdup(filesystemtype);
181
182 if (optstr)
183 m->optstr = strdup(optstr);
184
185 m->mountflags = mountflags;
186 m->propflags = propflags;
187 m->error = error;
188 m->inner = inner;
189
190 avl_insert(&mounts, &m->avl);
191 DEBUG("adding mount %s %s bind(%d) ro(%d) err(%d)\n", (m->source == (void*)(-1))?"mask":m->source, m->target,
192 !!(m->mountflags & MS_BIND), !!(m->mountflags & MS_RDONLY), m->error != 0);
193
194 return 0;
195 }
196
197 int add_mount(const char *source, const char *target, const char *filesystemtype,
198 unsigned long mountflags, unsigned long propflags, const char *optstr, int error)
199 {
200 return _add_mount(source, target, filesystemtype, mountflags, propflags, optstr, error, false);
201 }
202
203 int add_mount_inner(const char *source, const char *target, const char *filesystemtype,
204 unsigned long mountflags, unsigned long propflags, const char *optstr, int error)
205 {
206 return _add_mount(source, target, filesystemtype, mountflags, propflags, optstr, error, true);
207 }
208
209 int add_mount_bind(const char *path, int readonly, int error)
210 {
211 unsigned long mountflags = MS_BIND;
212
213 if (readonly)
214 mountflags |= MS_RDONLY;
215
216 return add_mount(path, path, NULL, mountflags, 0, NULL, error);
217 }
218
219 enum {
220 OCI_MOUNT_SOURCE,
221 OCI_MOUNT_DESTINATION,
222 OCI_MOUNT_TYPE,
223 OCI_MOUNT_OPTIONS,
224 __OCI_MOUNT_MAX,
225 };
226
227 static const struct blobmsg_policy oci_mount_policy[] = {
228 [OCI_MOUNT_SOURCE] = { "source", BLOBMSG_TYPE_STRING },
229 [OCI_MOUNT_DESTINATION] = { "destination", BLOBMSG_TYPE_STRING },
230 [OCI_MOUNT_TYPE] = { "type", BLOBMSG_TYPE_STRING },
231 [OCI_MOUNT_OPTIONS] = { "options", BLOBMSG_TYPE_ARRAY },
232 };
233
234 struct mount_opt {
235 struct list_head list;
236 char *optstr;
237 };
238
239 #ifndef MS_LAZYTIME
240 #define MS_LAZYTIME (1 << 25)
241 #endif
242
243 static int parseOCImountopts(struct blob_attr *msg, unsigned long *mount_flags, unsigned long *propagation_flags, char **mount_data, int *error)
244 {
245 struct blob_attr *cur;
246 int rem;
247 unsigned long mf = 0;
248 unsigned long pf = 0;
249 char *tmp;
250 struct list_head fsopts = LIST_HEAD_INIT(fsopts);
251 size_t len = 0;
252 struct mount_opt *opt, *tmpopt;
253
254 blobmsg_for_each_attr(cur, msg, rem) {
255 tmp = blobmsg_get_string(cur);
256 if (!strcmp("ro", tmp))
257 mf |= MS_RDONLY;
258 else if (!strcmp("rw", tmp))
259 mf &= ~MS_RDONLY;
260 else if (!strcmp("bind", tmp))
261 mf = MS_BIND;
262 else if (!strcmp("rbind", tmp))
263 mf |= MS_BIND | MS_REC;
264 else if (!strcmp("sync", tmp))
265 mf |= MS_SYNCHRONOUS;
266 else if (!strcmp("async", tmp))
267 mf &= ~MS_SYNCHRONOUS;
268 else if (!strcmp("atime", tmp))
269 mf &= ~MS_NOATIME;
270 else if (!strcmp("noatime", tmp))
271 mf |= MS_NOATIME;
272 else if (!strcmp("defaults", tmp))
273 mf = 0; /* rw, suid, dev, exec, auto, nouser, and async */
274 else if (!strcmp("dev", tmp))
275 mf &= ~MS_NODEV;
276 else if (!strcmp("nodev", tmp))
277 mf |= MS_NODEV;
278 else if (!strcmp("iversion", tmp))
279 mf |= MS_I_VERSION;
280 else if (!strcmp("noiversion", tmp))
281 mf &= ~MS_I_VERSION;
282 else if (!strcmp("diratime", tmp))
283 mf &= ~MS_NODIRATIME;
284 else if (!strcmp("nodiratime", tmp))
285 mf |= MS_NODIRATIME;
286 else if (!strcmp("dirsync", tmp))
287 mf |= MS_DIRSYNC;
288 else if (!strcmp("exec", tmp))
289 mf &= ~MS_NOEXEC;
290 else if (!strcmp("noexec", tmp))
291 mf |= MS_NOEXEC;
292 else if (!strcmp("mand", tmp))
293 mf |= MS_MANDLOCK;
294 else if (!strcmp("nomand", tmp))
295 mf &= ~MS_MANDLOCK;
296 else if (!strcmp("relatime", tmp))
297 mf |= MS_RELATIME;
298 else if (!strcmp("norelatime", tmp))
299 mf &= ~MS_RELATIME;
300 else if (!strcmp("strictatime", tmp))
301 mf |= MS_STRICTATIME;
302 else if (!strcmp("nostrictatime", tmp))
303 mf &= ~MS_STRICTATIME;
304 else if (!strcmp("lazytime", tmp))
305 mf |= MS_LAZYTIME;
306 else if (!strcmp("nolazytime", tmp))
307 mf &= ~MS_LAZYTIME;
308 else if (!strcmp("suid", tmp))
309 mf &= ~MS_NOSUID;
310 else if (!strcmp("nosuid", tmp))
311 mf |= MS_NOSUID;
312 else if (!strcmp("remount", tmp))
313 mf |= MS_REMOUNT;
314 /* propagation flags */
315 else if (!strcmp("private", tmp))
316 pf |= MS_PRIVATE;
317 else if (!strcmp("rprivate", tmp))
318 pf |= MS_PRIVATE | MS_REC;
319 else if (!strcmp("slave", tmp))
320 pf |= MS_SLAVE;
321 else if (!strcmp("rslave", tmp))
322 pf |= MS_SLAVE | MS_REC;
323 else if (!strcmp("shared", tmp))
324 pf |= MS_SHARED;
325 else if (!strcmp("rshared", tmp))
326 pf |= MS_SHARED | MS_REC;
327 else if (!strcmp("unbindable", tmp))
328 pf |= MS_UNBINDABLE;
329 else if (!strcmp("runbindable", tmp))
330 pf |= MS_UNBINDABLE | MS_REC;
331 /* special case: 'nofail' */
332 else if(!strcmp("nofail", tmp))
333 *error = 0;
334 else if (!strcmp("auto", tmp) ||
335 !strcmp("noauto", tmp) ||
336 !strcmp("user", tmp) ||
337 !strcmp("group", tmp) ||
338 !strcmp("_netdev", tmp))
339 DEBUG("ignoring built-in mount option %s\n", tmp);
340 else {
341 /* filesystem-specific free-form option */
342 opt = calloc(1, sizeof(*opt));
343 opt->optstr = tmp;
344 list_add_tail(&opt->list, &fsopts);
345 }
346 };
347
348 *mount_flags = mf;
349 *propagation_flags = pf;
350
351 list_for_each_entry(opt, &fsopts, list) {
352 if (len)
353 ++len;
354
355 len += strlen(opt->optstr);
356 };
357
358 if (len) {
359 *mount_data = calloc(len + 1, sizeof(char));
360 if (!mount_data)
361 return ENOMEM;
362
363 len = 0;
364 list_for_each_entry(opt, &fsopts, list) {
365 if (len)
366 strcat(*mount_data, ",");
367
368 strcat(*mount_data, opt->optstr);
369 ++len;
370 }
371
372 list_for_each_entry_safe(opt, tmpopt, &fsopts, list) {
373 list_del(&opt->list);
374 free(opt);
375 }
376 }
377
378 DEBUG("mount flags(%08lx) propagation(%08lx) fsopts(\"%s\")\n", mf, pf, *mount_data?:"");
379
380 return 0;
381 }
382
383 int parseOCImount(struct blob_attr *msg)
384 {
385 struct blob_attr *tb[__OCI_MOUNT_MAX];
386 unsigned long mount_flags = 0;
387 unsigned long propagation_flags = 0;
388 char *mount_data = NULL;
389 int ret, err = -1;
390
391 blobmsg_parse(oci_mount_policy, __OCI_MOUNT_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
392
393 if (!tb[OCI_MOUNT_DESTINATION])
394 return EINVAL;
395
396 if (tb[OCI_MOUNT_OPTIONS]) {
397 ret = parseOCImountopts(tb[OCI_MOUNT_OPTIONS], &mount_flags, &propagation_flags, &mount_data, &err);
398 if (ret)
399 return ret;
400 }
401
402 ret = add_mount(tb[OCI_MOUNT_SOURCE] ? blobmsg_get_string(tb[OCI_MOUNT_SOURCE]) : NULL,
403 blobmsg_get_string(tb[OCI_MOUNT_DESTINATION]),
404 tb[OCI_MOUNT_TYPE] ? blobmsg_get_string(tb[OCI_MOUNT_TYPE]) : NULL,
405 mount_flags, propagation_flags, mount_data, err);
406
407 if (mount_data)
408 free(mount_data);
409
410 return ret;
411 }
412
413 static void build_noafile(void) {
414 int fd;
415
416 fd = creat(UJAIL_NOAFILE, 0000);
417 if (fd == -1)
418 return;
419
420 close(fd);
421 return;
422 }
423
424 int mount_all(const char *jailroot) {
425 struct library *l;
426 struct mount *m;
427
428 build_noafile();
429
430 avl_for_each_element(&libraries, l, avl)
431 add_mount_bind(l->path, 1, -1);
432
433 avl_for_each_element(&mounts, m, avl)
434 if (do_mount(jailroot, m->source, m->target, m->filesystemtype, m->mountflags,
435 m->propflags, m->optstr, m->error, m->inner))
436 return -1;
437
438 return 0;
439 }
440
441 void mount_free(void) {
442 struct mount *m, *tmp;
443
444 avl_remove_all_elements(&mounts, m, avl, tmp) {
445 if (m->source != (void*)(-1))
446 free((void*)m->source);
447 free((void*)m->target);
448 free((void*)m->filesystemtype);
449 free((void*)m->optstr);
450 free(m);
451 }
452 }
453
454 void mount_list_init(void) {
455 avl_init(&mounts, avl_strcmp, false, NULL);
456 }
457
458 static int add_script_interp(const char *path, const char *map, int size)
459 {
460 int start = 2;
461 while (start < size && map[start] != '/') {
462 start++;
463 }
464 if (start >= size) {
465 ERROR("bad script interp (%s)\n", path);
466 return -1;
467 }
468 int stop = start + 1;
469 while (stop < size && map[stop] > 0x20 && map[stop] <= 0x7e) {
470 stop++;
471 }
472 if (stop >= size || (stop-start) > PATH_MAX) {
473 ERROR("bad script interp (%s)\n", path);
474 return -1;
475 }
476 char buf[PATH_MAX];
477 strncpy(buf, map+start, stop-start);
478 return add_path_and_deps(buf, 1, -1, 0);
479 }
480
481 int add_path_and_deps(const char *path, int readonly, int error, int lib)
482 {
483 assert(path != NULL);
484
485 if (lib == 0 && path[0] != '/') {
486 ERROR("%s is not an absolute path\n", path);
487 return error;
488 }
489
490 char *map = NULL;
491 int fd, ret = -1;
492 if (path[0] == '/') {
493 if (avl_find(&mounts, path))
494 return 0;
495 fd = open(path, O_RDONLY|O_CLOEXEC);
496 if (fd == -1)
497 return error;
498 add_mount_bind(path, readonly, error);
499 } else {
500 if (avl_find(&libraries, path))
501 return 0;
502 char *fullpath;
503 fd = lib_open(&fullpath, path);
504 if (fd == -1)
505 return error;
506 if (fullpath) {
507 alloc_library(fullpath, path);
508 free(fullpath);
509 }
510 }
511
512 struct stat s;
513 if (fstat(fd, &s) == -1) {
514 ERROR("fstat(%s) failed: %m\n", path);
515 ret = error;
516 goto out;
517 }
518
519 if (!S_ISREG(s.st_mode)) {
520 ret = 0;
521 goto out;
522 }
523
524 /* too small to be an ELF or a script -> "normal" file */
525 if (s.st_size < 4) {
526 ret = 0;
527 goto out;
528 }
529
530 map = mmap(NULL, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
531 if (map == MAP_FAILED) {
532 ERROR("failed to mmap %s: %m\n", path);
533 ret = -1;
534 goto out;
535 }
536
537 if (map[0] == '#' && map[1] == '!') {
538 ret = add_script_interp(path, map, s.st_size);
539 goto out;
540 }
541
542 if (map[0] == ELFMAG0 && map[1] == ELFMAG1 && map[2] == ELFMAG2 && map[3] == ELFMAG3) {
543 ret = elf_load_deps(path, map);
544 goto out;
545 }
546
547 ret = 0;
548
549 out:
550 if (fd >= 0)
551 close(fd);
552 if (map)
553 munmap(map, s.st_size);
554
555 return ret;
556 }