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>
35 #include <libubox/utils.h>
42 #define UJAIL_NOAFILE "/tmp/.ujailnoafile"
48 const char *filesystemtype
;
49 unsigned long mountflags
;
50 unsigned long propflags
;
56 struct avl_tree mounts
;
58 static int do_mount(const char *root
, const char *orig_source
, const char *target
, const char *filesystemtype
,
59 unsigned long orig_mountflags
, unsigned long propflags
, const char *optstr
, int error
, bool inner
)
63 char *source
= (char *)orig_source
;
65 bool is_bind
= (orig_mountflags
& MS_BIND
);
66 bool is_mask
= (source
== (void *)(-1));
67 unsigned long mountflags
= orig_mountflags
;
69 if (source
&& is_bind
&& stat(source
, &s
)) {
70 ERROR("stat(%s) failed: %m\n", source
);
74 if (!is_mask
&& orig_source
&& inner
) {
75 if (asprintf(&source
, "%s%s", root
, orig_source
) < 0)
79 snprintf(new, sizeof(new), "%s%s", root
, target
?target
:source
);
83 return 0; /* doesn't exists, nothing to mask */
85 if (S_ISDIR(s
.st_mode
)) {/* use empty 0-sized tmpfs for directories */
86 if (mount(NULL
, new, "tmpfs", MS_RDONLY
| MS_NOSUID
| MS_NOEXEC
| MS_NODEV
| MS_NOATIME
, "size=0,mode=000"))
89 /* mount-bind 0-sized file having mode 000 */
90 if (mount(UJAIL_NOAFILE
, new, "bind", MS_BIND
, NULL
))
93 if (mount(UJAIL_NOAFILE
, new, "bind", MS_REMOUNT
| MS_BIND
| MS_RDONLY
| MS_NOSUID
| MS_NOEXEC
| MS_NODEV
| MS_NOATIME
, NULL
))
97 DEBUG("masked path %s\n", new);
102 if (!is_bind
|| (source
&& S_ISDIR(s
.st_mode
))) {
104 } else if (is_bind
&& source
) {
105 mkdir_p(dirname(new), 0755);
106 snprintf(new, sizeof(new), "%s%s", root
, target
?target
:source
);
107 fd
= creat(new, 0644);
109 ERROR("creat(%s) failed: %m\n", new);
116 if (mount(source
?:new, new, filesystemtype
?:"bind", MS_BIND
| (mountflags
& MS_REC
), optstr
)) {
118 ERROR("failed to mount -B %s %s: %m\n", source
, new);
125 mountflags
|= MS_REMOUNT
;
128 const char *hack_fstype
= ((!filesystemtype
|| strcmp(filesystemtype
, "cgroup"))?filesystemtype
:"cgroup2");
129 if (mount(source
?:(is_bind
?new:NULL
), new, hack_fstype
?:"none", mountflags
, optstr
)) {
131 ERROR("failed to mount %s %s: %m\n", source
, new);
139 DEBUG("mount %s%s %s (%s)\n", (mountflags
& MS_BIND
)?"-B ":"", source
, new,
140 (mountflags
& MS_RDONLY
)?"ro":"rw");
142 if (propflags
&& mount("none", new, "none", propflags
, NULL
)) {
144 ERROR("failed to mount --make-... %s \n", new);
158 static int _add_mount(const char *source
, const char *target
, const char *filesystemtype
,
159 unsigned long mountflags
, unsigned long propflags
, const char *optstr
,
160 int error
, bool inner
)
162 assert(target
!= NULL
);
164 if (avl_find(&mounts
, target
))
168 m
= calloc(1, sizeof(struct mount
));
172 m
->avl
.key
= m
->target
= strdup(target
);
174 if (source
!= (void*)(-1))
175 m
->source
= strdup(source
);
177 m
->source
= (void*)(-1);
180 m
->filesystemtype
= strdup(filesystemtype
);
183 m
->optstr
= strdup(optstr
);
185 m
->mountflags
= mountflags
;
186 m
->propflags
= propflags
;
190 avl_insert(&mounts
, &m
->avl
);
191 DEBUG("adding mount %s %s bind(%d) ro(%d) err(%d)\n", (m
->source
== (void*)(-1))?"mask":m
->source
, m
->target
,
192 !!(m
->mountflags
& MS_BIND
), !!(m
->mountflags
& MS_RDONLY
), m
->error
!= 0);
197 int add_mount(const char *source
, const char *target
, const char *filesystemtype
,
198 unsigned long mountflags
, unsigned long propflags
, const char *optstr
, int error
)
200 return _add_mount(source
, target
, filesystemtype
, mountflags
, propflags
, optstr
, error
, false);
203 int add_mount_inner(const char *source
, const char *target
, const char *filesystemtype
,
204 unsigned long mountflags
, unsigned long propflags
, const char *optstr
, int error
)
206 return _add_mount(source
, target
, filesystemtype
, mountflags
, propflags
, optstr
, error
, true);
209 int add_mount_bind(const char *path
, int readonly
, int error
)
211 unsigned long mountflags
= MS_BIND
;
214 mountflags
|= MS_RDONLY
;
216 return add_mount(path
, path
, NULL
, mountflags
, 0, NULL
, error
);
221 OCI_MOUNT_DESTINATION
,
227 static const struct blobmsg_policy oci_mount_policy
[] = {
228 [OCI_MOUNT_SOURCE
] = { "source", BLOBMSG_TYPE_STRING
},
229 [OCI_MOUNT_DESTINATION
] = { "destination", BLOBMSG_TYPE_STRING
},
230 [OCI_MOUNT_TYPE
] = { "type", BLOBMSG_TYPE_STRING
},
231 [OCI_MOUNT_OPTIONS
] = { "options", BLOBMSG_TYPE_ARRAY
},
235 struct list_head list
;
240 #define MS_LAZYTIME (1 << 25)
243 static int parseOCImountopts(struct blob_attr
*msg
, unsigned long *mount_flags
, unsigned long *propagation_flags
, char **mount_data
, int *error
)
245 struct blob_attr
*cur
;
247 unsigned long mf
= 0;
248 unsigned long pf
= 0;
250 struct list_head fsopts
= LIST_HEAD_INIT(fsopts
);
252 struct mount_opt
*opt
, *tmpopt
;
254 blobmsg_for_each_attr(cur
, msg
, rem
) {
255 tmp
= blobmsg_get_string(cur
);
256 if (!strcmp("ro", tmp
))
258 else if (!strcmp("rw", tmp
))
260 else if (!strcmp("bind", tmp
))
262 else if (!strcmp("rbind", tmp
))
263 mf
|= MS_BIND
| MS_REC
;
264 else if (!strcmp("sync", tmp
))
265 mf
|= MS_SYNCHRONOUS
;
266 else if (!strcmp("async", tmp
))
267 mf
&= ~MS_SYNCHRONOUS
;
268 else if (!strcmp("atime", tmp
))
270 else if (!strcmp("noatime", tmp
))
272 else if (!strcmp("defaults", tmp
))
273 mf
= 0; /* rw, suid, dev, exec, auto, nouser, and async */
274 else if (!strcmp("dev", tmp
))
276 else if (!strcmp("nodev", tmp
))
278 else if (!strcmp("iversion", tmp
))
280 else if (!strcmp("noiversion", tmp
))
282 else if (!strcmp("diratime", tmp
))
283 mf
&= ~MS_NODIRATIME
;
284 else if (!strcmp("nodiratime", tmp
))
286 else if (!strcmp("dirsync", tmp
))
288 else if (!strcmp("exec", tmp
))
290 else if (!strcmp("noexec", tmp
))
292 else if (!strcmp("mand", tmp
))
294 else if (!strcmp("nomand", tmp
))
296 else if (!strcmp("relatime", tmp
))
298 else if (!strcmp("norelatime", tmp
))
300 else if (!strcmp("strictatime", tmp
))
301 mf
|= MS_STRICTATIME
;
302 else if (!strcmp("nostrictatime", tmp
))
303 mf
&= ~MS_STRICTATIME
;
304 else if (!strcmp("lazytime", tmp
))
306 else if (!strcmp("nolazytime", tmp
))
308 else if (!strcmp("suid", tmp
))
310 else if (!strcmp("nosuid", tmp
))
312 else if (!strcmp("remount", tmp
))
314 /* propagation flags */
315 else if (!strcmp("private", tmp
))
317 else if (!strcmp("rprivate", tmp
))
318 pf
|= MS_PRIVATE
| MS_REC
;
319 else if (!strcmp("slave", tmp
))
321 else if (!strcmp("rslave", tmp
))
322 pf
|= MS_SLAVE
| MS_REC
;
323 else if (!strcmp("shared", tmp
))
325 else if (!strcmp("rshared", tmp
))
326 pf
|= MS_SHARED
| MS_REC
;
327 else if (!strcmp("unbindable", tmp
))
329 else if (!strcmp("runbindable", tmp
))
330 pf
|= MS_UNBINDABLE
| MS_REC
;
331 /* special case: 'nofail' */
332 else if(!strcmp("nofail", tmp
))
334 else if (!strcmp("auto", tmp
) ||
335 !strcmp("noauto", tmp
) ||
336 !strcmp("user", tmp
) ||
337 !strcmp("group", tmp
) ||
338 !strcmp("_netdev", tmp
))
339 DEBUG("ignoring built-in mount option %s\n", tmp
);
341 /* filesystem-specific free-form option */
342 opt
= calloc(1, sizeof(*opt
));
344 list_add_tail(&opt
->list
, &fsopts
);
349 *propagation_flags
= pf
;
351 list_for_each_entry(opt
, &fsopts
, list
) {
355 len
+= strlen(opt
->optstr
);
359 *mount_data
= calloc(len
+ 1, sizeof(char));
364 list_for_each_entry(opt
, &fsopts
, list
) {
366 strcat(*mount_data
, ",");
368 strcat(*mount_data
, opt
->optstr
);
372 list_for_each_entry_safe(opt
, tmpopt
, &fsopts
, list
) {
373 list_del(&opt
->list
);
378 DEBUG("mount flags(%08lx) propagation(%08lx) fsopts(\"%s\")\n", mf
, pf
, *mount_data
?:"");
383 int parseOCImount(struct blob_attr
*msg
)
385 struct blob_attr
*tb
[__OCI_MOUNT_MAX
];
386 unsigned long mount_flags
= 0;
387 unsigned long propagation_flags
= 0;
388 char *mount_data
= NULL
;
391 blobmsg_parse(oci_mount_policy
, __OCI_MOUNT_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
393 if (!tb
[OCI_MOUNT_DESTINATION
])
396 if (tb
[OCI_MOUNT_OPTIONS
]) {
397 ret
= parseOCImountopts(tb
[OCI_MOUNT_OPTIONS
], &mount_flags
, &propagation_flags
, &mount_data
, &err
);
402 ret
= add_mount(tb
[OCI_MOUNT_SOURCE
] ? blobmsg_get_string(tb
[OCI_MOUNT_SOURCE
]) : NULL
,
403 blobmsg_get_string(tb
[OCI_MOUNT_DESTINATION
]),
404 tb
[OCI_MOUNT_TYPE
] ? blobmsg_get_string(tb
[OCI_MOUNT_TYPE
]) : NULL
,
405 mount_flags
, propagation_flags
, mount_data
, err
);
413 static void build_noafile(void) {
416 fd
= creat(UJAIL_NOAFILE
, 0000);
424 int mount_all(const char *jailroot
) {
430 avl_for_each_element(&libraries
, l
, avl
)
431 add_mount_bind(l
->path
, 1, -1);
433 avl_for_each_element(&mounts
, m
, avl
)
434 if (do_mount(jailroot
, m
->source
, m
->target
, m
->filesystemtype
, m
->mountflags
,
435 m
->propflags
, m
->optstr
, m
->error
, m
->inner
))
441 void mount_free(void) {
442 struct mount
*m
, *tmp
;
444 avl_remove_all_elements(&mounts
, m
, avl
, tmp
) {
445 if (m
->source
!= (void*)(-1))
446 free((void*)m
->source
);
447 free((void*)m
->target
);
448 free((void*)m
->filesystemtype
);
449 free((void*)m
->optstr
);
454 void mount_list_init(void) {
455 avl_init(&mounts
, avl_strcmp
, false, NULL
);
458 static int add_script_interp(const char *path
, const char *map
, int size
)
461 while (start
< size
&& map
[start
] != '/') {
465 ERROR("bad script interp (%s)\n", path
);
468 int stop
= start
+ 1;
469 while (stop
< size
&& map
[stop
] > 0x20 && map
[stop
] <= 0x7e) {
472 if (stop
>= size
|| (stop
-start
) > PATH_MAX
) {
473 ERROR("bad script interp (%s)\n", path
);
477 strncpy(buf
, map
+start
, stop
-start
);
478 return add_path_and_deps(buf
, 1, -1, 0);
481 int add_path_and_deps(const char *path
, int readonly
, int error
, int lib
)
483 assert(path
!= NULL
);
485 if (lib
== 0 && path
[0] != '/') {
486 ERROR("%s is not an absolute path\n", path
);
492 if (path
[0] == '/') {
493 if (avl_find(&mounts
, path
))
495 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
498 add_mount_bind(path
, readonly
, error
);
500 if (avl_find(&libraries
, path
))
503 fd
= lib_open(&fullpath
, path
);
507 alloc_library(fullpath
, path
);
513 if (fstat(fd
, &s
) == -1) {
514 ERROR("fstat(%s) failed: %m\n", path
);
519 if (!S_ISREG(s
.st_mode
)) {
524 /* too small to be an ELF or a script -> "normal" file */
530 map
= mmap(NULL
, s
.st_size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
531 if (map
== MAP_FAILED
) {
532 ERROR("failed to mmap %s: %m\n", path
);
537 if (map
[0] == '#' && map
[1] == '!') {
538 ret
= add_script_interp(path
, map
, s
.st_size
);
542 if (map
[0] == ELFMAG0
&& map
[1] == ELFMAG1
&& map
[2] == ELFMAG2
&& map
[3] == ELFMAG3
) {
543 ret
= elf_load_deps(path
, map
);
553 munmap(map
, s
.st_size
);