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 const char *hack_fstype
= ((!filesystemtype
|| strcmp(filesystemtype
, "cgroup"))?filesystemtype
:"cgroup2");
153 if (mount(source
?:(is_bind
?new:NULL
), new, hack_fstype
, 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(NULL
, new, NULL
, 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
;
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
);
397 DEBUG("mount flags(%08lx) propagation(%08lx) fsopts(\"%s\")\n", mf
, pf
, *mount_data
?:"");
402 int parseOCImount(struct blob_attr
*msg
)
404 struct blob_attr
*tb
[__OCI_MOUNT_MAX
];
405 unsigned long mount_flags
= 0;
406 unsigned long propagation_flags
= 0;
407 char *mount_data
= NULL
;
410 blobmsg_parse(oci_mount_policy
, __OCI_MOUNT_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
412 if (!tb
[OCI_MOUNT_DESTINATION
])
415 if (tb
[OCI_MOUNT_OPTIONS
]) {
416 ret
= parseOCImountopts(tb
[OCI_MOUNT_OPTIONS
], &mount_flags
, &propagation_flags
, &mount_data
, &err
);
421 ret
= add_mount(tb
[OCI_MOUNT_SOURCE
] ? blobmsg_get_string(tb
[OCI_MOUNT_SOURCE
]) : NULL
,
422 blobmsg_get_string(tb
[OCI_MOUNT_DESTINATION
]),
423 tb
[OCI_MOUNT_TYPE
] ? blobmsg_get_string(tb
[OCI_MOUNT_TYPE
]) : NULL
,
424 mount_flags
, propagation_flags
, mount_data
, err
);
432 static void build_noafile(void) {
435 fd
= creat(UJAIL_NOAFILE
, 0000);
443 int mount_all(const char *jailroot
) {
449 avl_for_each_element(&libraries
, l
, avl
)
450 add_mount_bind(l
->path
, 1, -1);
452 avl_for_each_element(&mounts
, m
, avl
)
453 if (do_mount(jailroot
, m
->source
, m
->target
, m
->filesystemtype
, m
->mountflags
,
454 m
->propflags
, m
->optstr
, m
->error
, m
->inner
))
460 void mount_list_init(void) {
461 avl_init(&mounts
, avl_strcmp
, false, NULL
);
464 static int add_script_interp(const char *path
, const char *map
, int size
)
467 while (start
< size
&& map
[start
] != '/') {
471 ERROR("bad script interp (%s)\n", path
);
474 int stop
= start
+ 1;
475 while (stop
< size
&& map
[stop
] > 0x20 && map
[stop
] <= 0x7e) {
478 if (stop
>= size
|| (stop
-start
) > PATH_MAX
) {
479 ERROR("bad script interp (%s)\n", path
);
483 strncpy(buf
, map
+start
, stop
-start
);
484 return add_path_and_deps(buf
, 1, -1, 0);
487 int add_path_and_deps(const char *path
, int readonly
, int error
, int lib
)
489 assert(path
!= 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
, path
))
501 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
504 add_mount_bind(path
, 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
);