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