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>
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
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.
22 #include <linux/limits.h>
31 #include <libubox/avl.h>
32 #include <libubox/avl-cmp.h>
33 #include <libubox/blobmsg.h>
34 #include <libubox/list.h>
41 #define UJAIL_NOAFILE "/tmp/.ujailnoafile"
47 const char *filesystemtype
;
48 unsigned long mountflags
;
49 unsigned long propflags
;
55 struct avl_tree mounts
;
57 int mkdir_p(char *dir
, mode_t mask
)
59 char *l
= strrchr(dir
, '/');
67 if (mkdir_p(dir
, mask
))
72 ret
= mkdir(dir
, mask
);
73 if (ret
&& errno
== EEXIST
)
77 ERROR("mkdir(%s, %d) failed: %m\n", dir
, mask
);
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
)
87 char *source
= (char *)orig_source
;
89 bool is_bind
= (orig_mountflags
& MS_BIND
);
90 bool is_mask
= (source
== (void *)(-1));
91 unsigned long mountflags
= orig_mountflags
;
93 if (source
&& is_bind
&& stat(source
, &s
)) {
94 ERROR("stat(%s) failed: %m\n", source
);
98 if (!is_mask
&& orig_source
&& inner
) {
99 if (asprintf(&source
, "%s%s", root
, orig_source
) < 0)
103 snprintf(new, sizeof(new), "%s%s", root
, target
?target
:source
);
107 return 0; /* doesn't exists, nothing to mask */
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"))
113 /* mount-bind 0-sized file having mode 000 */
114 if (mount(UJAIL_NOAFILE
, new, "bind", MS_BIND
, NULL
))
117 if (mount(UJAIL_NOAFILE
, new, "bind", MS_REMOUNT
| MS_BIND
| MS_RDONLY
| MS_NOSUID
| MS_NOEXEC
| MS_NODEV
| MS_NOATIME
, NULL
))
121 DEBUG("masked path %s\n", new);
126 if (!is_bind
|| (source
&& S_ISDIR(s
.st_mode
))) {
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);
133 ERROR("creat(%s) failed: %m\n", new);
140 if (mount(source
?:new, new, filesystemtype
?:"bind", MS_BIND
| (mountflags
& MS_REC
), optstr
)) {
142 ERROR("failed to mount -B %s %s: %m\n", source
, new);
149 mountflags
|= MS_REMOUNT
;
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
)) {
155 ERROR("failed to mount %s %s: %m\n", source
, new);
163 DEBUG("mount %s%s %s (%s)\n", (mountflags
& MS_BIND
)?"-B ":"", source
, new,
164 (mountflags
& MS_RDONLY
)?"ro":"rw");
166 if (propflags
&& mount("none", new, "none", propflags
, NULL
)) {
168 ERROR("failed to mount --make-... %s \n", new);
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
)
186 assert(target
!= NULL
);
188 if (avl_find(&mounts
, target
))
192 m
= calloc(1, sizeof(struct mount
));
194 m
->avl
.key
= m
->target
= strdup(target
);
196 if (source
!= (void*)(-1))
197 m
->source
= strdup(source
);
199 m
->source
= (void*)(-1);
202 m
->filesystemtype
= strdup(filesystemtype
);
205 m
->optstr
= strdup(optstr
);
207 m
->mountflags
= mountflags
;
208 m
->propflags
= propflags
;
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);
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
)
222 return _add_mount(source
, target
, filesystemtype
, mountflags
, propflags
, optstr
, error
, false);
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
)
228 return _add_mount(source
, target
, filesystemtype
, mountflags
, propflags
, optstr
, error
, true);
231 int add_mount_bind(const char *path
, int readonly
, int error
)
233 unsigned long mountflags
= MS_BIND
;
236 mountflags
|= MS_RDONLY
;
238 return add_mount(path
, path
, NULL
, mountflags
, 0, NULL
, error
);
243 OCI_MOUNT_DESTINATION
,
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
},
257 struct list_head list
;
262 #define MS_LAZYTIME (1 << 25)
265 static int parseOCImountopts(struct blob_attr
*msg
, unsigned long *mount_flags
, unsigned long *propagation_flags
, char **mount_data
, int *error
)
267 struct blob_attr
*cur
;
269 unsigned long mf
= 0;
270 unsigned long pf
= 0;
272 struct list_head fsopts
= LIST_HEAD_INIT(fsopts
);
274 struct mount_opt
*opt
, *tmpopt
;
276 blobmsg_for_each_attr(cur
, msg
, rem
) {
277 tmp
= blobmsg_get_string(cur
);
278 if (!strcmp("ro", tmp
))
280 else if (!strcmp("rw", tmp
))
282 else if (!strcmp("bind", tmp
))
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
))
292 else if (!strcmp("noatime", tmp
))
294 else if (!strcmp("defaults", tmp
))
295 mf
= 0; /* rw, suid, dev, exec, auto, nouser, and async */
296 else if (!strcmp("dev", tmp
))
298 else if (!strcmp("nodev", tmp
))
300 else if (!strcmp("iversion", tmp
))
302 else if (!strcmp("noiversion", tmp
))
304 else if (!strcmp("diratime", tmp
))
305 mf
&= ~MS_NODIRATIME
;
306 else if (!strcmp("nodiratime", tmp
))
308 else if (!strcmp("dirsync", tmp
))
310 else if (!strcmp("exec", tmp
))
312 else if (!strcmp("noexec", tmp
))
314 else if (!strcmp("mand", tmp
))
316 else if (!strcmp("nomand", tmp
))
318 else if (!strcmp("relatime", tmp
))
320 else if (!strcmp("norelatime", tmp
))
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
))
328 else if (!strcmp("nolazytime", tmp
))
330 else if (!strcmp("suid", tmp
))
332 else if (!strcmp("nosuid", tmp
))
334 else if (!strcmp("remount", tmp
))
336 /* propagation flags */
337 else if (!strcmp("private", tmp
))
339 else if (!strcmp("rprivate", tmp
))
340 pf
|= MS_PRIVATE
| MS_REC
;
341 else if (!strcmp("slave", tmp
))
343 else if (!strcmp("rslave", tmp
))
344 pf
|= MS_SLAVE
| MS_REC
;
345 else if (!strcmp("shared", tmp
))
347 else if (!strcmp("rshared", tmp
))
348 pf
|= MS_SHARED
| MS_REC
;
349 else if (!strcmp("unbindable", tmp
))
351 else if (!strcmp("runbindable", tmp
))
352 pf
|= MS_UNBINDABLE
| MS_REC
;
353 /* special case: 'nofail' */
354 else if(!strcmp("nofail", tmp
))
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
);
363 /* filesystem-specific free-form option */
364 opt
= calloc(1, sizeof(*opt
));
366 list_add_tail(&opt
->list
, &fsopts
);
371 *propagation_flags
= pf
;
373 list_for_each_entry(opt
, &fsopts
, list
) {
377 len
+= strlen(opt
->optstr
);
381 *mount_data
= calloc(len
+ 1, sizeof(char));
386 list_for_each_entry(opt
, &fsopts
, list
) {
388 strcat(*mount_data
, ",");
390 strcat(*mount_data
, opt
->optstr
);
394 list_for_each_entry_safe(opt
, tmpopt
, &fsopts
, list
) {
395 list_del(&opt
->list
);
400 DEBUG("mount flags(%08lx) propagation(%08lx) fsopts(\"%s\")\n", mf
, pf
, *mount_data
?:"");
405 int parseOCImount(struct blob_attr
*msg
)
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
;
413 blobmsg_parse(oci_mount_policy
, __OCI_MOUNT_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
415 if (!tb
[OCI_MOUNT_DESTINATION
])
418 if (tb
[OCI_MOUNT_OPTIONS
]) {
419 ret
= parseOCImountopts(tb
[OCI_MOUNT_OPTIONS
], &mount_flags
, &propagation_flags
, &mount_data
, &err
);
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
);
435 static void build_noafile(void) {
438 fd
= creat(UJAIL_NOAFILE
, 0000);
446 int mount_all(const char *jailroot
) {
452 avl_for_each_element(&libraries
, l
, avl
)
453 add_mount_bind(l
->path
, 1, -1);
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
))
463 void mount_free(void) {
464 struct mount
*m
, *tmp
;
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
);
476 void mount_list_init(void) {
477 avl_init(&mounts
, avl_strcmp
, false, NULL
);
480 static int add_script_interp(const char *path
, const char *map
, int size
)
483 while (start
< size
&& map
[start
] != '/') {
487 ERROR("bad script interp (%s)\n", path
);
490 int stop
= start
+ 1;
491 while (stop
< size
&& map
[stop
] > 0x20 && map
[stop
] <= 0x7e) {
494 if (stop
>= size
|| (stop
-start
) > PATH_MAX
) {
495 ERROR("bad script interp (%s)\n", path
);
499 strncpy(buf
, map
+start
, stop
-start
);
500 return add_path_and_deps(buf
, 1, -1, 0);
503 int add_path_and_deps(const char *path
, int readonly
, int error
, int lib
)
505 assert(path
!= NULL
);
507 if (lib
== 0 && path
[0] != '/') {
508 ERROR("%s is not an absolute path\n", path
);
514 if (path
[0] == '/') {
515 if (avl_find(&mounts
, path
))
517 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
520 add_mount_bind(path
, readonly
, error
);
522 if (avl_find(&libraries
, path
))
525 fd
= lib_open(&fullpath
, path
);
529 alloc_library(fullpath
, path
);
535 if (fstat(fd
, &s
) == -1) {
536 ERROR("fstat(%s) failed: %m\n", path
);
541 if (!S_ISREG(s
.st_mode
)) {
546 /* too small to be an ELF or a script -> "normal" file */
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
);
559 if (map
[0] == '#' && map
[1] == '!') {
560 ret
= add_script_interp(path
, map
, s
.st_size
);
564 if (map
[0] == ELFMAG0
&& map
[1] == ELFMAG1
&& map
[2] == ELFMAG2
&& map
[3] == ELFMAG3
) {
565 ret
= elf_load_deps(path
, map
);
575 munmap(map
, s
.st_size
);