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
);
173 m
->mountflags
= mountflags
;
176 avl_insert(&mounts
, &m
->avl
);
177 DEBUG("adding mount %s %s bind(%d) ro(%d) err(%d)\n", (m
->source
== (void*)(-1))?"mask":m
->source
, m
->target
,
178 !!(m
->mountflags
& MS_BIND
), !!(m
->mountflags
& MS_RDONLY
), m
->error
!= 0);
183 int add_mount_bind(const char *path
, int readonly
, int error
)
185 unsigned long mountflags
= MS_BIND
;
188 mountflags
|= MS_RDONLY
;
190 return add_mount(path
, path
, NULL
, mountflags
, NULL
, error
);
195 OCI_MOUNT_DESTINATION
,
201 static const struct blobmsg_policy oci_mount_policy
[] = {
202 [OCI_MOUNT_SOURCE
] = { "source", BLOBMSG_TYPE_STRING
},
203 [OCI_MOUNT_DESTINATION
] = { "destination", BLOBMSG_TYPE_STRING
},
204 [OCI_MOUNT_TYPE
] = { "type", BLOBMSG_TYPE_STRING
},
205 [OCI_MOUNT_OPTIONS
] = { "options", BLOBMSG_TYPE_ARRAY
},
209 struct list_head list
;
214 #define MS_LAZYTIME (1 << 25)
217 static int parseOCImountopts(struct blob_attr
*msg
, unsigned long *mount_flags
, char **mount_data
, int *error
)
219 struct blob_attr
*cur
;
221 unsigned long mf
= 0;
223 struct list_head fsopts
= LIST_HEAD_INIT(fsopts
);
225 struct mount_opt
*opt
;
227 blobmsg_for_each_attr(cur
, msg
, rem
) {
228 tmp
= blobmsg_get_string(cur
);
229 if (!strcmp("ro", tmp
))
231 else if (!strcmp("rw", tmp
))
233 else if (!strcmp("bind", tmp
))
235 else if (!strcmp("rbind", tmp
))
236 mf
|= MS_BIND
| MS_REC
;
237 else if (!strcmp("sync", tmp
))
238 mf
|= MS_SYNCHRONOUS
;
239 else if (!strcmp("async", tmp
))
240 mf
&= ~MS_SYNCHRONOUS
;
241 else if (!strcmp("atime", tmp
))
243 else if (!strcmp("noatime", tmp
))
245 else if (!strcmp("defaults", tmp
))
246 mf
= 0; /* rw, suid, dev, exec, auto, nouser, and async */
247 else if (!strcmp("dev", tmp
))
249 else if (!strcmp("nodev", tmp
))
251 else if (!strcmp("iversion", tmp
))
253 else if (!strcmp("noiversion", tmp
))
255 else if (!strcmp("diratime", tmp
))
256 mf
&= ~MS_NODIRATIME
;
257 else if (!strcmp("nodiratime", tmp
))
259 else if (!strcmp("dirsync", tmp
))
261 else if (!strcmp("exec", tmp
))
263 else if (!strcmp("noexec", tmp
))
265 else if (!strcmp("mand", tmp
))
267 else if (!strcmp("nomand", tmp
))
269 else if (!strcmp("relatime", tmp
))
271 else if (!strcmp("norelatime", tmp
))
273 else if (!strcmp("strictatime", tmp
))
274 mf
|= MS_STRICTATIME
;
275 else if (!strcmp("nostrictatime", tmp
))
276 mf
&= ~MS_STRICTATIME
;
277 else if (!strcmp("lazytime", tmp
))
279 else if (!strcmp("nolazytime", tmp
))
281 else if (!strcmp("suid", tmp
))
283 else if (!strcmp("nosuid", tmp
))
285 else if (!strcmp("remount", tmp
))
287 else if(!strcmp("nofail", tmp
))
289 else if (!strcmp("auto", tmp
) ||
290 !strcmp("noauto", tmp
) ||
291 !strcmp("user", tmp
) ||
292 !strcmp("group", tmp
) ||
293 !strcmp("_netdev", tmp
))
294 DEBUG("ignoring built-in mount option %s\n", tmp
);
296 /* filesystem-specific free-form option */
297 opt
= calloc(1, sizeof(*opt
));
299 list_add_tail(&opt
->list
, &fsopts
);
305 list_for_each_entry(opt
, &fsopts
, list
) {
309 len
+= strlen(opt
->optstr
);
315 *mount_data
= calloc(len
+ 1, sizeof(char));
320 list_for_each_entry(opt
, &fsopts
, list
) {
322 strcat(*mount_data
, ",");
324 strcat(*mount_data
, opt
->optstr
);
330 DEBUG("mount flags(%08lx) fsopts(\"%s\")\n", mf
, *mount_data
?:"");
335 int parseOCImount(struct blob_attr
*msg
)
337 struct blob_attr
*tb
[__OCI_MOUNT_MAX
];
338 unsigned long mount_flags
= 0;
339 char *mount_data
= NULL
;
342 blobmsg_parse(oci_mount_policy
, __OCI_MOUNT_MAX
, tb
, blobmsg_data(msg
), blobmsg_len(msg
));
344 if (!tb
[OCI_MOUNT_DESTINATION
])
347 if (tb
[OCI_MOUNT_OPTIONS
]) {
348 ret
= parseOCImountopts(tb
[OCI_MOUNT_OPTIONS
], &mount_flags
, &mount_data
, &err
);
353 return add_mount(tb
[OCI_MOUNT_SOURCE
] ? blobmsg_get_string(tb
[OCI_MOUNT_SOURCE
]) : NULL
,
354 blobmsg_get_string(tb
[OCI_MOUNT_DESTINATION
]),
355 tb
[OCI_MOUNT_TYPE
] ? blobmsg_get_string(tb
[OCI_MOUNT_TYPE
]) : NULL
,
356 mount_flags
, mount_data
, err
);
359 static void build_noafile(void) {
362 fd
= creat(UJAIL_NOAFILE
, 0000);
370 int mount_all(const char *jailroot
) {
376 avl_for_each_element(&libraries
, l
, avl
)
377 add_mount_bind(l
->path
, 1, -1);
379 avl_for_each_element(&mounts
, m
, avl
)
380 if (do_mount(jailroot
, m
->source
, m
->target
, m
->filesystemtype
, m
->mountflags
, m
->optstr
, m
->error
))
386 void mount_list_init(void) {
387 avl_init(&mounts
, avl_strcmp
, false, NULL
);
390 static int add_script_interp(const char *path
, const char *map
, int size
)
393 while (start
< size
&& map
[start
] != '/') {
397 ERROR("bad script interp (%s)\n", path
);
400 int stop
= start
+ 1;
401 while (stop
< size
&& map
[stop
] > 0x20 && map
[stop
] <= 0x7e) {
404 if (stop
>= size
|| (stop
-start
) > PATH_MAX
) {
405 ERROR("bad script interp (%s)\n", path
);
409 strncpy(buf
, map
+start
, stop
-start
);
410 return add_path_and_deps(buf
, 1, -1, 0);
413 int add_path_and_deps(const char *path
, int readonly
, int error
, int lib
)
415 assert(path
!= NULL
);
417 if (lib
== 0 && path
[0] != '/') {
418 ERROR("%s is not an absolute path\n", path
);
424 if (path
[0] == '/') {
425 if (avl_find(&mounts
, path
))
427 fd
= open(path
, O_RDONLY
|O_CLOEXEC
);
430 add_mount_bind(path
, readonly
, error
);
432 if (avl_find(&libraries
, path
))
435 fd
= lib_open(&fullpath
, path
);
439 alloc_library(fullpath
, path
);
445 if (fstat(fd
, &s
) == -1) {
446 ERROR("fstat(%s) failed: %m\n", path
);
451 if (!S_ISREG(s
.st_mode
)) {
456 /* too small to be an ELF or a script -> "normal" file */
462 map
= mmap(NULL
, s
.st_size
, PROT_READ
, MAP_PRIVATE
, fd
, 0);
463 if (map
== MAP_FAILED
) {
464 ERROR("failed to mmap %s: %m\n", path
);
469 if (map
[0] == '#' && map
[1] == '!') {
470 ret
= add_script_interp(path
, map
, s
.st_size
);
474 if (map
[0] == ELFMAG0
&& map
[1] == ELFMAG1
&& map
[2] == ELFMAG2
&& map
[3] == ELFMAG3
) {
475 ret
= elf_load_deps(path
, map
);
485 munmap(map
, s
.st_size
);