jail: add support for maskedPaths and readonlyPaths
[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 m->mountflags = mountflags;
174 m->error = error;
175
176 avl_insert(&mounts, &m->avl);
177 DEBUG("adding mount %s %s bind(%d) ro(%d) err(%d)\n", (m->source == (void*)(-1))?"mask":m->source, m->target,
178 !!(m->mountflags & MS_BIND), !!(m->mountflags & MS_RDONLY), m->error != 0);
179
180 return 0;
181 }
182
183 int add_mount_bind(const char *path, int readonly, int error)
184 {
185 unsigned long mountflags = MS_BIND;
186
187 if (readonly)
188 mountflags |= MS_RDONLY;
189
190 return add_mount(path, path, NULL, mountflags, NULL, error);
191 }
192
193 enum {
194 OCI_MOUNT_SOURCE,
195 OCI_MOUNT_DESTINATION,
196 OCI_MOUNT_TYPE,
197 OCI_MOUNT_OPTIONS,
198 __OCI_MOUNT_MAX,
199 };
200
201 static const struct blobmsg_policy oci_mount_policy[] = {
202 [OCI_MOUNT_SOURCE] = { "source", BLOBMSG_TYPE_STRING },
203 [OCI_MOUNT_DESTINATION] = { "destination", BLOBMSG_TYPE_STRING },
204 [OCI_MOUNT_TYPE] = { "type", BLOBMSG_TYPE_STRING },
205 [OCI_MOUNT_OPTIONS] = { "options", BLOBMSG_TYPE_ARRAY },
206 };
207
208 struct mount_opt {
209 struct list_head list;
210 char *optstr;
211 };
212
213 #ifndef MS_LAZYTIME
214 #define MS_LAZYTIME (1 << 25)
215 #endif
216
217 static int parseOCImountopts(struct blob_attr *msg, unsigned long *mount_flags, char **mount_data, int *error)
218 {
219 struct blob_attr *cur;
220 int rem;
221 unsigned long mf = 0;
222 char *tmp;
223 struct list_head fsopts = LIST_HEAD_INIT(fsopts);
224 size_t len = 0;
225 struct mount_opt *opt;
226
227 blobmsg_for_each_attr(cur, msg, rem) {
228 tmp = blobmsg_get_string(cur);
229 if (!strcmp("ro", tmp))
230 mf |= MS_RDONLY;
231 else if (!strcmp("rw", tmp))
232 mf &= ~MS_RDONLY;
233 else if (!strcmp("bind", tmp))
234 mf = MS_BIND;
235 else if (!strcmp("rbind", tmp))
236 mf |= MS_BIND | MS_REC;
237 else if (!strcmp("sync", tmp))
238 mf |= MS_SYNCHRONOUS;
239 else if (!strcmp("async", tmp))
240 mf &= ~MS_SYNCHRONOUS;
241 else if (!strcmp("atime", tmp))
242 mf &= ~MS_NOATIME;
243 else if (!strcmp("noatime", tmp))
244 mf |= MS_NOATIME;
245 else if (!strcmp("defaults", tmp))
246 mf = 0; /* rw, suid, dev, exec, auto, nouser, and async */
247 else if (!strcmp("dev", tmp))
248 mf &= ~MS_NODEV;
249 else if (!strcmp("nodev", tmp))
250 mf |= MS_NODEV;
251 else if (!strcmp("iversion", tmp))
252 mf |= MS_I_VERSION;
253 else if (!strcmp("noiversion", tmp))
254 mf &= ~MS_I_VERSION;
255 else if (!strcmp("diratime", tmp))
256 mf &= ~MS_NODIRATIME;
257 else if (!strcmp("nodiratime", tmp))
258 mf |= MS_NODIRATIME;
259 else if (!strcmp("dirsync", tmp))
260 mf |= MS_DIRSYNC;
261 else if (!strcmp("exec", tmp))
262 mf &= ~MS_NOEXEC;
263 else if (!strcmp("noexec", tmp))
264 mf |= MS_NOEXEC;
265 else if (!strcmp("mand", tmp))
266 mf |= MS_MANDLOCK;
267 else if (!strcmp("nomand", tmp))
268 mf &= ~MS_MANDLOCK;
269 else if (!strcmp("relatime", tmp))
270 mf |= MS_RELATIME;
271 else if (!strcmp("norelatime", tmp))
272 mf &= ~MS_RELATIME;
273 else if (!strcmp("strictatime", tmp))
274 mf |= MS_STRICTATIME;
275 else if (!strcmp("nostrictatime", tmp))
276 mf &= ~MS_STRICTATIME;
277 else if (!strcmp("lazytime", tmp))
278 mf |= MS_LAZYTIME;
279 else if (!strcmp("nolazytime", tmp))
280 mf &= ~MS_LAZYTIME;
281 else if (!strcmp("suid", tmp))
282 mf &= ~MS_NOSUID;
283 else if (!strcmp("nosuid", tmp))
284 mf |= MS_NOSUID;
285 else if (!strcmp("remount", tmp))
286 mf |= MS_REMOUNT;
287 else if(!strcmp("nofail", tmp))
288 *error = 0;
289 else if (!strcmp("auto", tmp) ||
290 !strcmp("noauto", tmp) ||
291 !strcmp("user", tmp) ||
292 !strcmp("group", tmp) ||
293 !strcmp("_netdev", tmp))
294 DEBUG("ignoring built-in mount option %s\n", tmp);
295 else {
296 /* filesystem-specific free-form option */
297 opt = calloc(1, sizeof(*opt));
298 opt->optstr = tmp;
299 list_add_tail(&opt->list, &fsopts);
300 }
301 };
302
303 *mount_flags = mf;
304
305 list_for_each_entry(opt, &fsopts, list) {
306 if (len)
307 ++len;
308
309 len += strlen(opt->optstr);
310 };
311
312 if (!len)
313 return 0;
314
315 *mount_data = calloc(len + 1, sizeof(char));
316 if (!mount_data)
317 return ENOMEM;
318
319 len = 0;
320 list_for_each_entry(opt, &fsopts, list) {
321 if (len)
322 strcat(*mount_data, ",");
323
324 strcat(*mount_data, opt->optstr);
325 ++len;
326 };
327
328 list_del(&fsopts);
329
330 DEBUG("mount flags(%08lx) fsopts(\"%s\")\n", mf, *mount_data?:"");
331
332 return 0;
333 }
334
335 int parseOCImount(struct blob_attr *msg)
336 {
337 struct blob_attr *tb[__OCI_MOUNT_MAX];
338 unsigned long mount_flags = 0;
339 char *mount_data = NULL;
340 int ret, err = -1;
341
342 blobmsg_parse(oci_mount_policy, __OCI_MOUNT_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
343
344 if (!tb[OCI_MOUNT_DESTINATION])
345 return EINVAL;
346
347 if (tb[OCI_MOUNT_OPTIONS]) {
348 ret = parseOCImountopts(tb[OCI_MOUNT_OPTIONS], &mount_flags, &mount_data, &err);
349 if (ret)
350 return ret;
351 }
352
353 return add_mount(tb[OCI_MOUNT_SOURCE] ? blobmsg_get_string(tb[OCI_MOUNT_SOURCE]) : NULL,
354 blobmsg_get_string(tb[OCI_MOUNT_DESTINATION]),
355 tb[OCI_MOUNT_TYPE] ? blobmsg_get_string(tb[OCI_MOUNT_TYPE]) : NULL,
356 mount_flags, mount_data, err);
357 }
358
359 static void build_noafile(void) {
360 int fd;
361
362 fd = creat(UJAIL_NOAFILE, 0000);
363 if (fd == -1)
364 return;
365
366 close(fd);
367 return;
368 }
369
370 int mount_all(const char *jailroot) {
371 struct library *l;
372 struct mount *m;
373
374 build_noafile();
375
376 avl_for_each_element(&libraries, l, avl)
377 add_mount_bind(l->path, 1, -1);
378
379 avl_for_each_element(&mounts, m, avl)
380 if (do_mount(jailroot, m->source, m->target, m->filesystemtype, m->mountflags, m->optstr, m->error))
381 return -1;
382
383 return 0;
384 }
385
386 void mount_list_init(void) {
387 avl_init(&mounts, avl_strcmp, false, NULL);
388 }
389
390 static int add_script_interp(const char *path, const char *map, int size)
391 {
392 int start = 2;
393 while (start < size && map[start] != '/') {
394 start++;
395 }
396 if (start >= size) {
397 ERROR("bad script interp (%s)\n", path);
398 return -1;
399 }
400 int stop = start + 1;
401 while (stop < size && map[stop] > 0x20 && map[stop] <= 0x7e) {
402 stop++;
403 }
404 if (stop >= size || (stop-start) > PATH_MAX) {
405 ERROR("bad script interp (%s)\n", path);
406 return -1;
407 }
408 char buf[PATH_MAX];
409 strncpy(buf, map+start, stop-start);
410 return add_path_and_deps(buf, 1, -1, 0);
411 }
412
413 int add_path_and_deps(const char *path, int readonly, int error, int lib)
414 {
415 assert(path != NULL);
416
417 if (lib == 0 && path[0] != '/') {
418 ERROR("%s is not an absolute path\n", path);
419 return error;
420 }
421
422 char *map = NULL;
423 int fd, ret = -1;
424 if (path[0] == '/') {
425 if (avl_find(&mounts, path))
426 return 0;
427 fd = open(path, O_RDONLY|O_CLOEXEC);
428 if (fd == -1)
429 return error;
430 add_mount_bind(path, readonly, error);
431 } else {
432 if (avl_find(&libraries, path))
433 return 0;
434 char *fullpath;
435 fd = lib_open(&fullpath, path);
436 if (fd == -1)
437 return error;
438 if (fullpath) {
439 alloc_library(fullpath, path);
440 free(fullpath);
441 }
442 }
443
444 struct stat s;
445 if (fstat(fd, &s) == -1) {
446 ERROR("fstat(%s) failed: %m\n", path);
447 ret = error;
448 goto out;
449 }
450
451 if (!S_ISREG(s.st_mode)) {
452 ret = 0;
453 goto out;
454 }
455
456 /* too small to be an ELF or a script -> "normal" file */
457 if (s.st_size < 4) {
458 ret = 0;
459 goto out;
460 }
461
462 map = mmap(NULL, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
463 if (map == MAP_FAILED) {
464 ERROR("failed to mmap %s: %m\n", path);
465 ret = -1;
466 goto out;
467 }
468
469 if (map[0] == '#' && map[1] == '!') {
470 ret = add_script_interp(path, map, s.st_size);
471 goto out;
472 }
473
474 if (map[0] == ELFMAG0 && map[1] == ELFMAG1 && map[2] == ELFMAG2 && map[3] == ELFMAG3) {
475 ret = elf_load_deps(path, map);
476 goto out;
477 }
478
479 ret = 0;
480
481 out:
482 if (fd >= 0)
483 close(fd);
484 if (map)
485 munmap(map, s.st_size);
486
487 return ret;
488 }