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 static int _add_mount_bind(const char *path
, const char *path2
, int readonly
, int error
)
211 unsigned long mountflags
= MS_BIND
;
214 mountflags
|= MS_RDONLY
;
216 return add_mount(path
, path2
, NULL
, mountflags
, 0, NULL
, error
);
219 int add_mount_bind(const char *path
, int readonly
, int error
)
221 return _add_mount_bind(path
, path
, readonly
, error
);
226 OCI_MOUNT_DESTINATION
,
232 static const struct blobmsg_policy oci_mount_policy
[] = {
233 [OCI_MOUNT_SOURCE
] = { "source", BLOBMSG_TYPE_STRING
},
234 [OCI_MOUNT_DESTINATION
] = { "destination", BLOBMSG_TYPE_STRING
},
235 [OCI_MOUNT_TYPE
] = { "type", BLOBMSG_TYPE_STRING
},
236 [OCI_MOUNT_OPTIONS
] = { "options", BLOBMSG_TYPE_ARRAY
},
240 struct list_head list
;
245 #define MS_LAZYTIME (1 << 25)
248 static int parseOCImountopts(struct blob_attr
*msg
, unsigned long *mount_flags
, unsigned long *propagation_flags
, char **mount_data
, int *error
)
250 struct blob_attr
*cur
;
252 unsigned long mf
= 0;
253 unsigned long pf
= 0;
255 struct list_head fsopts
= LIST_HEAD_INIT(fsopts
);
257 struct mount_opt
*opt
, *tmpopt
;
259 blobmsg_for_each_attr(cur
, msg
, rem
) {
260 tmp
= blobmsg_get_string(cur
);
261 if (!strcmp("ro", tmp
))
263 else if (!strcmp("rw", tmp
))
265 else if (!strcmp("bind", tmp
))
267 else if (!strcmp("rbind", tmp
))
268 mf
|= MS_BIND
| MS_REC
;
269 else if (!strcmp("sync", tmp
))
270 mf
|= MS_SYNCHRONOUS
;
271 else if (!strcmp("async", tmp
))
272 mf
&= ~MS_SYNCHRONOUS
;
273 else if (!strcmp("atime", tmp
))
275 else if (!strcmp("noatime", tmp
))
277 else if (!strcmp("defaults", tmp
))
278 mf
= 0; /* rw, suid, dev, exec, auto, nouser, and async */
279 else if (!strcmp("dev", tmp
))
281 else if (!strcmp("nodev", tmp
))
283 else if (!strcmp("iversion", tmp
))
285 else if (!strcmp("noiversion", tmp
))
287 else if (!strcmp("diratime", tmp
))
288 mf
&= ~MS_NODIRATIME
;
289 else if (!strcmp("nodiratime", tmp
))
291 else if (!strcmp("dirsync", tmp
))
293 else if (!strcmp("exec", tmp
))
295 else if (!strcmp("noexec", tmp
))
297 else if (!strcmp("mand", tmp
))
299 else if (!strcmp("nomand", tmp
))
301 else if (!strcmp("relatime", tmp
))
303 else if (!strcmp("norelatime", tmp
))
305 else if (!strcmp("strictatime", tmp
))
306 mf
|= MS_STRICTATIME
;
307 else if (!strcmp("nostrictatime", tmp
))
308 mf
&= ~MS_STRICTATIME
;
309 else if (!strcmp("lazytime", tmp
))
311 else if (!strcmp("nolazytime", tmp
))
313 else if (!strcmp("suid", tmp
))
315 else if (!strcmp("nosuid", tmp
))
317 else if (!strcmp("remount", tmp
))
319 /* propagation flags */
320 else if (!strcmp("private", tmp
))
322 else if (!strcmp("rprivate", tmp
))
323 pf
|= MS_PRIVATE
| MS_REC
;
324 else if (!strcmp("slave", tmp
))
326 else if (!strcmp("rslave", tmp
))
327 pf
|= MS_SLAVE
| MS_REC
;
328 else if (!strcmp("shared", tmp
))
330 else if (!strcmp("rshared", tmp
))
331 pf
|= MS_SHARED
| MS_REC
;
332 else if (!strcmp("unbindable", tmp
))
334 else if (!strcmp("runbindable", tmp
))
335 pf
|= MS_UNBINDABLE
| MS_REC
;
336 /* special case: 'nofail' */
337 else if(!strcmp("nofail", tmp
))
339 else if (!strcmp("auto", tmp
) ||
340 !strcmp("noauto", tmp
) ||
341 !strcmp("user", tmp
) ||
342 !strcmp("group", tmp
) ||
343 !strcmp("_netdev", tmp
))
344 DEBUG("ignoring built-in mount option %s\n", tmp
);
346 /* filesystem-specific free-form option */
347 opt
= calloc(1, sizeof(*opt
));
349 list_add_tail(&opt
->list
, &fsopts
);
354 *propagation_flags
= pf
;
356 list_for_each_entry(opt
, &fsopts
, list
) {
360 len
+= strlen(opt
->optstr
);
364 *mount_data
= calloc(len
+ 1, sizeof(char));
369 list_for_each_entry(opt
, &fsopts
, list
) {
371 strcat(*mount_data
, ",");
373 strcat(*mount_data
, opt
->optstr
);
377 list_for_each_entry_safe(opt
, tmpopt
, &fsopts
, list
) {
378 list_del(&opt
->list
);
383 DEBUG("mount flags(%08lx) propagation(%08lx) fsopts(\"%s\")\n", mf
, pf
, *mount_data
?:"");
388 int parseOCImount(struct blob_attr
*msg
)
390 struct blob_attr
*tb
[__OCI_MOUNT_MAX
];
391 unsigned long mount_flags
= 0;
392 unsigned long propagation_flags
= 0;
393 char *mount_data
= NULL
;
396 blobmsg_parse(oci_mount_policy
, __OCI_MOUNT_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
398 if (!tb
[OCI_MOUNT_DESTINATION
])
401 if (tb
[OCI_MOUNT_OPTIONS
]) {
402 ret
= parseOCImountopts(tb
[OCI_MOUNT_OPTIONS
], &mount_flags
, &propagation_flags
, &mount_data
, &err
);
407 ret
= add_mount(tb
[OCI_MOUNT_SOURCE
] ? blobmsg_get_string(tb
[OCI_MOUNT_SOURCE
]) : NULL
,
408 blobmsg_get_string(tb
[OCI_MOUNT_DESTINATION
]),
409 tb
[OCI_MOUNT_TYPE
] ? blobmsg_get_string(tb
[OCI_MOUNT_TYPE
]) : NULL
,
410 mount_flags
, propagation_flags
, mount_data
, err
);
418 static void build_noafile(void) {
421 fd
= creat(UJAIL_NOAFILE
, 0000);
429 int mount_all(const char *jailroot
) {
435 avl_for_each_element(&libraries
, l
, avl
)
436 add_mount_bind(l
->path
, 1, -1);
438 avl_for_each_element(&mounts
, m
, avl
)
439 if (do_mount(jailroot
, m
->source
, m
->target
, m
->filesystemtype
, m
->mountflags
,
440 m
->propflags
, m
->optstr
, m
->error
, m
->inner
))
446 void mount_free(void) {
447 struct mount
*m
, *tmp
;
449 avl_remove_all_elements(&mounts
, m
, avl
, tmp
) {
450 if (m
->source
!= (void*)(-1))
451 free((void*)m
->source
);
452 free((void*)m
->target
);
453 free((void*)m
->filesystemtype
);
454 free((void*)m
->optstr
);
459 void mount_list_init(void) {
460 avl_init(&mounts
, avl_strcmp
, false, NULL
);
463 static int add_script_interp(const char *path
, const char *map
, int size
)
466 while (start
< size
&& map
[start
] != '/') {
470 ERROR("bad script interp (%s)\n", path
);
473 int stop
= start
+ 1;
474 while (stop
< size
&& map
[stop
] > 0x20 && map
[stop
] <= 0x7e) {
477 if (stop
>= size
|| (stop
-start
) > PATH_MAX
) {
478 ERROR("bad script interp (%s)\n", path
);
482 strncpy(buf
, map
+start
, stop
-start
);
483 return add_path_and_deps(buf
, 1, -1, 0);
486 int add_2paths_and_deps(const char *path
, const char *path2
, int readonly
, int error
, int lib
)
488 assert(path
!= NULL
);
489 assert(path2
!= NULL
);
491 if (lib
== 0 && path
[0] != '/') {
492 ERROR("%s is not an absolute path\n", path
);
498 if (path
[0] == '/') {
499 if (avl_find(&mounts
, path2
))
501 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
504 _add_mount_bind(path
, path2
, readonly
, error
);
506 if (avl_find(&libraries
, path
))
509 fd
= lib_open(&fullpath
, path
);
513 alloc_library(fullpath
, path
);
519 if (fstat(fd
, &s
) == -1) {
520 ERROR("fstat(%s) failed: %m\n", path
);
525 if (!S_ISREG(s
.st_mode
)) {
530 /* too small to be an ELF or a script -> "normal" file */
536 map
= mmap(NULL
, s
.st_size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
537 if (map
== MAP_FAILED
) {
538 ERROR("failed to mmap %s: %m\n", path
);
543 if (map
[0] == '#' && map
[1] == '!') {
544 ret
= add_script_interp(path
, map
, s
.st_size
);
548 if (map
[0] == ELFMAG0
&& map
[1] == ELFMAG1
&& map
[2] == ELFMAG2
&& map
[3] == ELFMAG3
) {
549 ret
= elf_load_deps(path
, map
);
559 munmap(map
, s
.st_size
);