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