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, NULL
, MS_BIND
, NULL
))
117 if (mount(UJAIL_NOAFILE
, new, NULL
, 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
, MS_BIND
| (mountflags
& MS_REC
), optstr
)) {
142 ERROR("failed to mount -B %s %s: %m\n", source
, new);
149 mountflags
|= MS_REMOUNT
;
152 if (mount(source
?:(is_bind
?new:NULL
), new, filesystemtype
, mountflags
, optstr
)) {
154 ERROR("failed to mount %s %s: %m\n", source
, new);
162 DEBUG("mount %s%s %s (%s)\n", (mountflags
& MS_BIND
)?"-B ":"", source
, new,
163 (mountflags
& MS_RDONLY
)?"ro":"rw");
165 if (propflags
&& mount(NULL
, new, NULL
, propflags
, NULL
)) {
167 ERROR("failed to mount --make-... %s \n", new);
181 static int _add_mount(const char *source
, const char *target
, const char *filesystemtype
,
182 unsigned long mountflags
, unsigned long propflags
, const char *optstr
,
183 int error
, bool inner
)
185 assert(target
!= NULL
);
187 if (avl_find(&mounts
, target
))
191 m
= calloc(1, sizeof(struct mount
));
193 m
->avl
.key
= m
->target
= strdup(target
);
195 if (source
!= (void*)(-1))
196 m
->source
= strdup(source
);
198 m
->source
= (void*)(-1);
201 m
->filesystemtype
= strdup(filesystemtype
);
204 m
->optstr
= strdup(optstr
);
206 m
->mountflags
= mountflags
;
207 m
->propflags
= propflags
;
211 avl_insert(&mounts
, &m
->avl
);
212 DEBUG("adding mount %s %s bind(%d) ro(%d) err(%d)\n", (m
->source
== (void*)(-1))?"mask":m
->source
, m
->target
,
213 !!(m
->mountflags
& MS_BIND
), !!(m
->mountflags
& MS_RDONLY
), m
->error
!= 0);
218 int add_mount(const char *source
, const char *target
, const char *filesystemtype
,
219 unsigned long mountflags
, unsigned long propflags
, const char *optstr
, int error
)
221 return _add_mount(source
, target
, filesystemtype
, mountflags
, propflags
, optstr
, error
, false);
224 int add_mount_inner(const char *source
, const char *target
, const char *filesystemtype
,
225 unsigned long mountflags
, unsigned long propflags
, const char *optstr
, int error
)
227 return _add_mount(source
, target
, filesystemtype
, mountflags
, propflags
, optstr
, error
, true);
230 int add_mount_bind(const char *path
, int readonly
, int error
)
232 unsigned long mountflags
= MS_BIND
;
235 mountflags
|= MS_RDONLY
;
237 return add_mount(path
, path
, NULL
, mountflags
, 0, NULL
, error
);
242 OCI_MOUNT_DESTINATION
,
248 static const struct blobmsg_policy oci_mount_policy
[] = {
249 [OCI_MOUNT_SOURCE
] = { "source", BLOBMSG_TYPE_STRING
},
250 [OCI_MOUNT_DESTINATION
] = { "destination", BLOBMSG_TYPE_STRING
},
251 [OCI_MOUNT_TYPE
] = { "type", BLOBMSG_TYPE_STRING
},
252 [OCI_MOUNT_OPTIONS
] = { "options", BLOBMSG_TYPE_ARRAY
},
256 struct list_head list
;
261 #define MS_LAZYTIME (1 << 25)
264 static int parseOCImountopts(struct blob_attr
*msg
, unsigned long *mount_flags
, unsigned long *propagation_flags
, char **mount_data
, int *error
)
266 struct blob_attr
*cur
;
268 unsigned long mf
= 0;
269 unsigned long pf
= 0;
271 struct list_head fsopts
= LIST_HEAD_INIT(fsopts
);
273 struct mount_opt
*opt
;
275 blobmsg_for_each_attr(cur
, msg
, rem
) {
276 tmp
= blobmsg_get_string(cur
);
277 if (!strcmp("ro", tmp
))
279 else if (!strcmp("rw", tmp
))
281 else if (!strcmp("bind", tmp
))
283 else if (!strcmp("rbind", tmp
))
284 mf
|= MS_BIND
| MS_REC
;
285 else if (!strcmp("sync", tmp
))
286 mf
|= MS_SYNCHRONOUS
;
287 else if (!strcmp("async", tmp
))
288 mf
&= ~MS_SYNCHRONOUS
;
289 else if (!strcmp("atime", tmp
))
291 else if (!strcmp("noatime", tmp
))
293 else if (!strcmp("defaults", tmp
))
294 mf
= 0; /* rw, suid, dev, exec, auto, nouser, and async */
295 else if (!strcmp("dev", tmp
))
297 else if (!strcmp("nodev", tmp
))
299 else if (!strcmp("iversion", tmp
))
301 else if (!strcmp("noiversion", tmp
))
303 else if (!strcmp("diratime", tmp
))
304 mf
&= ~MS_NODIRATIME
;
305 else if (!strcmp("nodiratime", tmp
))
307 else if (!strcmp("dirsync", tmp
))
309 else if (!strcmp("exec", tmp
))
311 else if (!strcmp("noexec", tmp
))
313 else if (!strcmp("mand", tmp
))
315 else if (!strcmp("nomand", tmp
))
317 else if (!strcmp("relatime", tmp
))
319 else if (!strcmp("norelatime", tmp
))
321 else if (!strcmp("strictatime", tmp
))
322 mf
|= MS_STRICTATIME
;
323 else if (!strcmp("nostrictatime", tmp
))
324 mf
&= ~MS_STRICTATIME
;
325 else if (!strcmp("lazytime", tmp
))
327 else if (!strcmp("nolazytime", tmp
))
329 else if (!strcmp("suid", tmp
))
331 else if (!strcmp("nosuid", tmp
))
333 else if (!strcmp("remount", tmp
))
335 /* propagation flags */
336 else if (!strcmp("private", tmp
))
338 else if (!strcmp("rprivate", tmp
))
339 pf
|= MS_PRIVATE
| MS_REC
;
340 else if (!strcmp("slave", tmp
))
342 else if (!strcmp("rslave", tmp
))
343 pf
|= MS_SLAVE
| MS_REC
;
344 else if (!strcmp("shared", tmp
))
346 else if (!strcmp("rshared", tmp
))
347 pf
|= MS_SHARED
| MS_REC
;
348 else if (!strcmp("unbindable", tmp
))
350 else if (!strcmp("runbindable", tmp
))
351 pf
|= MS_UNBINDABLE
| MS_REC
;
352 /* special case: 'nofail' */
353 else if(!strcmp("nofail", tmp
))
355 else if (!strcmp("auto", tmp
) ||
356 !strcmp("noauto", tmp
) ||
357 !strcmp("user", tmp
) ||
358 !strcmp("group", tmp
) ||
359 !strcmp("_netdev", tmp
))
360 DEBUG("ignoring built-in mount option %s\n", tmp
);
362 /* filesystem-specific free-form option */
363 opt
= calloc(1, sizeof(*opt
));
365 list_add_tail(&opt
->list
, &fsopts
);
370 *propagation_flags
= pf
;
372 list_for_each_entry(opt
, &fsopts
, list
) {
376 len
+= strlen(opt
->optstr
);
380 *mount_data
= calloc(len
+ 1, sizeof(char));
385 list_for_each_entry(opt
, &fsopts
, list
) {
387 strcat(*mount_data
, ",");
389 strcat(*mount_data
, opt
->optstr
);
396 DEBUG("mount flags(%08lx) propagation(%08lx) fsopts(\"%s\")\n", mf
, pf
, *mount_data
?:"");
401 int parseOCImount(struct blob_attr
*msg
)
403 struct blob_attr
*tb
[__OCI_MOUNT_MAX
];
404 unsigned long mount_flags
= 0;
405 unsigned long propagation_flags
= 0;
406 char *mount_data
= NULL
;
409 blobmsg_parse(oci_mount_policy
, __OCI_MOUNT_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
411 if (!tb
[OCI_MOUNT_DESTINATION
])
414 if (tb
[OCI_MOUNT_OPTIONS
]) {
415 ret
= parseOCImountopts(tb
[OCI_MOUNT_OPTIONS
], &mount_flags
, &propagation_flags
, &mount_data
, &err
);
420 ret
= add_mount(tb
[OCI_MOUNT_SOURCE
] ? blobmsg_get_string(tb
[OCI_MOUNT_SOURCE
]) : NULL
,
421 blobmsg_get_string(tb
[OCI_MOUNT_DESTINATION
]),
422 tb
[OCI_MOUNT_TYPE
] ? blobmsg_get_string(tb
[OCI_MOUNT_TYPE
]) : NULL
,
423 mount_flags
, propagation_flags
, mount_data
, err
);
431 static void build_noafile(void) {
434 fd
= creat(UJAIL_NOAFILE
, 0000);
442 int mount_all(const char *jailroot
) {
448 avl_for_each_element(&libraries
, l
, avl
)
449 add_mount_bind(l
->path
, 1, -1);
451 avl_for_each_element(&mounts
, m
, avl
)
452 if (do_mount(jailroot
, m
->source
, m
->target
, m
->filesystemtype
, m
->mountflags
,
453 m
->propflags
, m
->optstr
, m
->error
, m
->inner
))
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_path_and_deps(const char *path
, int readonly
, int error
, int lib
)
488 assert(path
!= NULL
);
490 if (lib
== 0 && path
[0] != '/') {
491 ERROR("%s is not an absolute path\n", path
);
497 if (path
[0] == '/') {
498 if (avl_find(&mounts
, path
))
500 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
503 add_mount_bind(path
, readonly
, error
);
505 if (avl_find(&libraries
, path
))
508 fd
= lib_open(&fullpath
, path
);
512 alloc_library(fullpath
, path
);
518 if (fstat(fd
, &s
) == -1) {
519 ERROR("fstat(%s) failed: %m\n", path
);
524 if (!S_ISREG(s
.st_mode
)) {
529 /* too small to be an ELF or a script -> "normal" file */
535 map
= mmap(NULL
, s
.st_size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
536 if (map
== MAP_FAILED
) {
537 ERROR("failed to mmap %s: %m\n", path
);
542 if (map
[0] == '#' && map
[1] == '!') {
543 ret
= add_script_interp(path
, map
, s
.st_size
);
547 if (map
[0] == ELFMAG0
&& map
[1] == ELFMAG1
&& map
[2] == ELFMAG2
&& map
[3] == ELFMAG3
) {
548 ret
= elf_load_deps(path
, map
);
558 munmap(map
, s
.st_size
);