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>
30 #include <libubox/avl.h>
31 #include <libubox/avl-cmp.h>
32 #include <libubox/blobmsg.h>
33 #include <libubox/list.h>
40 #define UJAIL_NOAFILE "/tmp/.ujailnoafile"
46 const char *filesystemtype
;
47 unsigned long mountflags
;
52 struct avl_tree mounts
;
54 int mkdir_p(char *dir
, mode_t mask
)
56 char *l
= strrchr(dir
, '/');
64 if (mkdir_p(dir
, mask
))
69 ret
= mkdir(dir
, mask
);
70 if (ret
&& errno
== EEXIST
)
74 ERROR("mkdir(%s, %d) failed: %m\n", dir
, mask
);
79 static int do_mount(const char *root
, const char *source
, const char *target
, const char *filesystemtype
,
80 unsigned long orig_mountflags
, const char *optstr
, int error
)
85 bool is_bind
= (orig_mountflags
& MS_BIND
);
86 bool is_mask
= (source
== (void *)(-1));
87 unsigned long mountflags
= orig_mountflags
;
89 if (source
&& is_bind
&& stat(source
, &s
)) {
90 ERROR("stat(%s) failed: %m\n", source
);
94 snprintf(new, sizeof(new), "%s%s", root
, target
?target
:source
);
98 return 0; /* doesn't exists, nothing to mask */
100 if (S_ISDIR(s
.st_mode
)) {/* use empty 0-sized tmpfs for directories */
101 if (mount(NULL
, new, "tmpfs", MS_RDONLY
| MS_NOSUID
| MS_NOEXEC
| MS_NODEV
| MS_NOATIME
, "size=0,mode=000"))
104 /* mount-bind 0-sized file having mode 000 */
105 if (mount(UJAIL_NOAFILE
, new, NULL
, MS_BIND
, NULL
))
108 if (mount(UJAIL_NOAFILE
, new, NULL
, MS_REMOUNT
| MS_BIND
| MS_RDONLY
| MS_NOSUID
| MS_NOEXEC
| MS_NODEV
| MS_NOATIME
, NULL
))
112 DEBUG("masked path %s\n", new);
117 if (!is_bind
|| (source
&& S_ISDIR(s
.st_mode
))) {
119 } else if (is_bind
&& source
) {
120 mkdir_p(dirname(new), 0755);
121 snprintf(new, sizeof(new), "%s%s", root
, target
?target
:source
);
122 fd
= creat(new, 0644);
124 ERROR("creat(%s) failed: %m\n", new);
131 if (mount(source
?:new, new, filesystemtype
, MS_BIND
| (mountflags
& MS_REC
), optstr
)) {
133 ERROR("failed to mount -B %s %s: %m\n", source
, new);
137 mountflags
|= MS_REMOUNT
;
140 if (mount(source
?:(is_bind
?new:NULL
), new, filesystemtype
, mountflags
, optstr
)) {
142 ERROR("failed to mount %s %s: %m\n", source
, new);
147 DEBUG("mount %s%s %s (%s)\n", (mountflags
& MS_BIND
)?"-B ":"", source
, new,
148 (mountflags
& MS_RDONLY
)?"ro":"rw");
153 int add_mount(const char *source
, const char *target
, const char *filesystemtype
,
154 unsigned long mountflags
, const char *optstr
, int error
)
156 assert(target
!= NULL
);
158 if (avl_find(&mounts
, target
))
162 m
= calloc(1, sizeof(struct mount
));
164 m
->avl
.key
= m
->target
= strdup(target
);
166 if (source
!= (void*)(-1))
167 m
->source
= strdup(source
);
169 m
->source
= (void*)(-1);
172 m
->filesystemtype
= strdup(filesystemtype
);
175 m
->optstr
= strdup(optstr
);
177 m
->mountflags
= mountflags
;
180 avl_insert(&mounts
, &m
->avl
);
181 DEBUG("adding mount %s %s bind(%d) ro(%d) err(%d)\n", (m
->source
== (void*)(-1))?"mask":m
->source
, m
->target
,
182 !!(m
->mountflags
& MS_BIND
), !!(m
->mountflags
& MS_RDONLY
), m
->error
!= 0);
187 int add_mount_bind(const char *path
, int readonly
, int error
)
189 unsigned long mountflags
= MS_BIND
;
192 mountflags
|= MS_RDONLY
;
194 return add_mount(path
, path
, NULL
, mountflags
, NULL
, error
);
199 OCI_MOUNT_DESTINATION
,
205 static const struct blobmsg_policy oci_mount_policy
[] = {
206 [OCI_MOUNT_SOURCE
] = { "source", BLOBMSG_TYPE_STRING
},
207 [OCI_MOUNT_DESTINATION
] = { "destination", BLOBMSG_TYPE_STRING
},
208 [OCI_MOUNT_TYPE
] = { "type", BLOBMSG_TYPE_STRING
},
209 [OCI_MOUNT_OPTIONS
] = { "options", BLOBMSG_TYPE_ARRAY
},
213 struct list_head list
;
218 #define MS_LAZYTIME (1 << 25)
221 static int parseOCImountopts(struct blob_attr
*msg
, unsigned long *mount_flags
, char **mount_data
, int *error
)
223 struct blob_attr
*cur
;
225 unsigned long mf
= 0;
227 struct list_head fsopts
= LIST_HEAD_INIT(fsopts
);
229 struct mount_opt
*opt
;
231 blobmsg_for_each_attr(cur
, msg
, rem
) {
232 tmp
= blobmsg_get_string(cur
);
233 if (!strcmp("ro", tmp
))
235 else if (!strcmp("rw", tmp
))
237 else if (!strcmp("bind", tmp
))
239 else if (!strcmp("rbind", tmp
))
240 mf
|= MS_BIND
| MS_REC
;
241 else if (!strcmp("sync", tmp
))
242 mf
|= MS_SYNCHRONOUS
;
243 else if (!strcmp("async", tmp
))
244 mf
&= ~MS_SYNCHRONOUS
;
245 else if (!strcmp("atime", tmp
))
247 else if (!strcmp("noatime", tmp
))
249 else if (!strcmp("defaults", tmp
))
250 mf
= 0; /* rw, suid, dev, exec, auto, nouser, and async */
251 else if (!strcmp("dev", tmp
))
253 else if (!strcmp("nodev", tmp
))
255 else if (!strcmp("iversion", tmp
))
257 else if (!strcmp("noiversion", tmp
))
259 else if (!strcmp("diratime", tmp
))
260 mf
&= ~MS_NODIRATIME
;
261 else if (!strcmp("nodiratime", tmp
))
263 else if (!strcmp("dirsync", tmp
))
265 else if (!strcmp("exec", tmp
))
267 else if (!strcmp("noexec", tmp
))
269 else if (!strcmp("mand", tmp
))
271 else if (!strcmp("nomand", tmp
))
273 else if (!strcmp("relatime", tmp
))
275 else if (!strcmp("norelatime", tmp
))
277 else if (!strcmp("strictatime", tmp
))
278 mf
|= MS_STRICTATIME
;
279 else if (!strcmp("nostrictatime", tmp
))
280 mf
&= ~MS_STRICTATIME
;
281 else if (!strcmp("lazytime", tmp
))
283 else if (!strcmp("nolazytime", tmp
))
285 else if (!strcmp("suid", tmp
))
287 else if (!strcmp("nosuid", tmp
))
289 else if (!strcmp("remount", tmp
))
291 else if(!strcmp("nofail", tmp
))
293 else if (!strcmp("auto", tmp
) ||
294 !strcmp("noauto", tmp
) ||
295 !strcmp("user", tmp
) ||
296 !strcmp("group", tmp
) ||
297 !strcmp("_netdev", tmp
))
298 DEBUG("ignoring built-in mount option %s\n", tmp
);
300 /* filesystem-specific free-form option */
301 opt
= calloc(1, sizeof(*opt
));
303 list_add_tail(&opt
->list
, &fsopts
);
309 list_for_each_entry(opt
, &fsopts
, list
) {
313 len
+= strlen(opt
->optstr
);
319 *mount_data
= calloc(len
+ 1, sizeof(char));
324 list_for_each_entry(opt
, &fsopts
, list
) {
326 strcat(*mount_data
, ",");
328 strcat(*mount_data
, opt
->optstr
);
334 DEBUG("mount flags(%08lx) fsopts(\"%s\")\n", mf
, *mount_data
?:"");
339 int parseOCImount(struct blob_attr
*msg
)
341 struct blob_attr
*tb
[__OCI_MOUNT_MAX
];
342 unsigned long mount_flags
= 0;
343 char *mount_data
= NULL
;
346 blobmsg_parse(oci_mount_policy
, __OCI_MOUNT_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
348 if (!tb
[OCI_MOUNT_DESTINATION
])
351 if (tb
[OCI_MOUNT_OPTIONS
]) {
352 ret
= parseOCImountopts(tb
[OCI_MOUNT_OPTIONS
], &mount_flags
, &mount_data
, &err
);
357 ret
= add_mount(tb
[OCI_MOUNT_SOURCE
] ? blobmsg_get_string(tb
[OCI_MOUNT_SOURCE
]) : NULL
,
358 blobmsg_get_string(tb
[OCI_MOUNT_DESTINATION
]),
359 tb
[OCI_MOUNT_TYPE
] ? blobmsg_get_string(tb
[OCI_MOUNT_TYPE
]) : NULL
,
360 mount_flags
, mount_data
, err
);
368 static void build_noafile(void) {
371 fd
= creat(UJAIL_NOAFILE
, 0000);
379 int mount_all(const char *jailroot
) {
385 avl_for_each_element(&libraries
, l
, avl
)
386 add_mount_bind(l
->path
, 1, -1);
388 avl_for_each_element(&mounts
, m
, avl
)
389 if (do_mount(jailroot
, m
->source
, m
->target
, m
->filesystemtype
, m
->mountflags
, m
->optstr
, m
->error
))
395 void mount_list_init(void) {
396 avl_init(&mounts
, avl_strcmp
, false, NULL
);
399 static int add_script_interp(const char *path
, const char *map
, int size
)
402 while (start
< size
&& map
[start
] != '/') {
406 ERROR("bad script interp (%s)\n", path
);
409 int stop
= start
+ 1;
410 while (stop
< size
&& map
[stop
] > 0x20 && map
[stop
] <= 0x7e) {
413 if (stop
>= size
|| (stop
-start
) > PATH_MAX
) {
414 ERROR("bad script interp (%s)\n", path
);
418 strncpy(buf
, map
+start
, stop
-start
);
419 return add_path_and_deps(buf
, 1, -1, 0);
422 int add_path_and_deps(const char *path
, int readonly
, int error
, int lib
)
424 assert(path
!= NULL
);
426 if (lib
== 0 && path
[0] != '/') {
427 ERROR("%s is not an absolute path\n", path
);
433 if (path
[0] == '/') {
434 if (avl_find(&mounts
, path
))
436 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
439 add_mount_bind(path
, readonly
, error
);
441 if (avl_find(&libraries
, path
))
444 fd
= lib_open(&fullpath
, path
);
448 alloc_library(fullpath
, path
);
454 if (fstat(fd
, &s
) == -1) {
455 ERROR("fstat(%s) failed: %m\n", path
);
460 if (!S_ISREG(s
.st_mode
)) {
465 /* too small to be an ELF or a script -> "normal" file */
471 map
= mmap(NULL
, s
.st_size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
472 if (map
== MAP_FAILED
) {
473 ERROR("failed to mmap %s: %m\n", path
);
478 if (map
[0] == '#' && map
[1] == '!') {
479 ret
= add_script_interp(path
, map
, s
.st_size
);
483 if (map
[0] == ELFMAG0
&& map
[1] == ELFMAG1
&& map
[2] == ELFMAG2
&& map
[3] == ELFMAG3
) {
484 ret
= elf_load_deps(path
, map
);
494 munmap(map
, s
.st_size
);