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