123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562 |
- /*
- * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
- * Copyright (C) 2015 Etienne Champetier <champetier.etienne@gmail.com>
- * Copyright (C) 2020 Daniel Golle <daniel@makrotopia.org>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU Lesser General Public License version 2.1
- * as published by the Free Software Foundation
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- */
- #define _GNU_SOURCE
- #include <assert.h>
- #include <elf.h>
- #include <errno.h>
- #include <fcntl.h>
- #include <linux/limits.h>
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- #include <sys/stat.h>
- #include <sys/mman.h>
- #include <unistd.h>
- #include <libgen.h>
- #include <libubox/avl.h>
- #include <libubox/avl-cmp.h>
- #include <libubox/blobmsg.h>
- #include <libubox/list.h>
- #include <libubox/utils.h>
- #include "elf.h"
- #include "fs.h"
- #include "jail.h"
- #include "log.h"
- #define UJAIL_NOAFILE "/tmp/.ujailnoafile"
- struct mount {
- struct avl_node avl;
- const char *source;
- const char *target;
- const char *filesystemtype;
- unsigned long mountflags;
- unsigned long propflags;
- const char *optstr;
- int error;
- bool inner;
- };
- struct avl_tree mounts;
- static int do_mount(const char *root, const char *orig_source, const char *target, const char *filesystemtype,
- unsigned long orig_mountflags, unsigned long propflags, const char *optstr, int error, bool inner)
- {
- struct stat s;
- char new[PATH_MAX];
- char *source = (char *)orig_source;
- int fd;
- bool is_bind = (orig_mountflags & MS_BIND);
- bool is_mask = (source == (void *)(-1));
- unsigned long mountflags = orig_mountflags;
- if (source && is_bind && stat(source, &s)) {
- ERROR("stat(%s) failed: %m\n", source);
- return error;
- }
- if (!is_mask && orig_source && inner) {
- if (asprintf(&source, "%s%s", root, orig_source) < 0)
- return ENOMEM;
- }
- snprintf(new, sizeof(new), "%s%s", root, target?target:source);
- if (is_mask) {
- if (stat(new, &s))
- return 0; /* doesn't exists, nothing to mask */
- if (S_ISDIR(s.st_mode)) {/* use empty 0-sized tmpfs for directories */
- if (mount(NULL, new, "tmpfs", MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_NOATIME, "size=0,mode=000"))
- return error;
- } else {
- /* mount-bind 0-sized file having mode 000 */
- if (mount(UJAIL_NOAFILE, new, "bind", MS_BIND, NULL))
- return error;
- if (mount(UJAIL_NOAFILE, new, "bind", MS_REMOUNT | MS_BIND | MS_RDONLY | MS_NOSUID | MS_NOEXEC | MS_NODEV | MS_NOATIME, NULL))
- return error;
- }
- DEBUG("masked path %s\n", new);
- return 0;
- }
- if (!is_bind || (source && S_ISDIR(s.st_mode))) {
- mkdir_p(new, 0755);
- } else if (is_bind && source) {
- mkdir_p(dirname(new), 0755);
- snprintf(new, sizeof(new), "%s%s", root, target?target:source);
- fd = creat(new, 0644);
- if (fd < 0) {
- ERROR("creat(%s) failed: %m\n", new);
- return error;
- }
- close(fd);
- }
- if (is_bind) {
- if (mount(source?:new, new, filesystemtype?:"bind", MS_BIND | (mountflags & MS_REC), optstr)) {
- if (error)
- ERROR("failed to mount -B %s %s: %m\n", source, new);
- if (inner)
- free(source);
- return error;
- }
- mountflags |= MS_REMOUNT;
- }
- const char *hack_fstype = ((!filesystemtype || strcmp(filesystemtype, "cgroup"))?filesystemtype:"cgroup2");
- if (mount(source?:(is_bind?new:NULL), new, hack_fstype?:"none", mountflags, optstr)) {
- if (error)
- ERROR("failed to mount %s %s: %m\n", source, new);
- if (inner)
- free(source);
- return error;
- }
- DEBUG("mount %s%s %s (%s)\n", (mountflags & MS_BIND)?"-B ":"", source, new,
- (mountflags & MS_RDONLY)?"ro":"rw");
- if (propflags && mount("none", new, "none", propflags, NULL)) {
- if (error)
- ERROR("failed to mount --make-... %s \n", new);
- if (inner)
- free(source);
- return error;
- }
- if (inner)
- free(source);
- return 0;
- }
- static int _add_mount(const char *source, const char *target, const char *filesystemtype,
- unsigned long mountflags, unsigned long propflags, const char *optstr,
- int error, bool inner)
- {
- assert(target != NULL);
- if (avl_find(&mounts, target))
- return 1;
- struct mount *m;
- m = calloc(1, sizeof(struct mount));
- if (!m)
- return ENOMEM;
- m->avl.key = m->target = strdup(target);
- if (source) {
- if (source != (void*)(-1))
- m->source = strdup(source);
- else
- m->source = (void*)(-1);
- }
- if (filesystemtype)
- m->filesystemtype = strdup(filesystemtype);
- if (optstr)
- m->optstr = strdup(optstr);
- m->mountflags = mountflags;
- m->propflags = propflags;
- m->error = error;
- m->inner = inner;
- avl_insert(&mounts, &m->avl);
- DEBUG("adding mount %s %s bind(%d) ro(%d) err(%d)\n", (m->source == (void*)(-1))?"mask":m->source, m->target,
- !!(m->mountflags & MS_BIND), !!(m->mountflags & MS_RDONLY), m->error != 0);
- return 0;
- }
- int add_mount(const char *source, const char *target, const char *filesystemtype,
- unsigned long mountflags, unsigned long propflags, const char *optstr, int error)
- {
- return _add_mount(source, target, filesystemtype, mountflags, propflags, optstr, error, false);
- }
- int add_mount_inner(const char *source, const char *target, const char *filesystemtype,
- unsigned long mountflags, unsigned long propflags, const char *optstr, int error)
- {
- return _add_mount(source, target, filesystemtype, mountflags, propflags, optstr, error, true);
- }
- static int _add_mount_bind(const char *path, const char *path2, int readonly, int error)
- {
- unsigned long mountflags = MS_BIND;
- if (readonly)
- mountflags |= MS_RDONLY;
- return add_mount(path, path2, NULL, mountflags, 0, NULL, error);
- }
- int add_mount_bind(const char *path, int readonly, int error)
- {
- return _add_mount_bind(path, path, readonly, error);
- }
- enum {
- OCI_MOUNT_SOURCE,
- OCI_MOUNT_DESTINATION,
- OCI_MOUNT_TYPE,
- OCI_MOUNT_OPTIONS,
- __OCI_MOUNT_MAX,
- };
- static const struct blobmsg_policy oci_mount_policy[] = {
- [OCI_MOUNT_SOURCE] = { "source", BLOBMSG_TYPE_STRING },
- [OCI_MOUNT_DESTINATION] = { "destination", BLOBMSG_TYPE_STRING },
- [OCI_MOUNT_TYPE] = { "type", BLOBMSG_TYPE_STRING },
- [OCI_MOUNT_OPTIONS] = { "options", BLOBMSG_TYPE_ARRAY },
- };
- struct mount_opt {
- struct list_head list;
- char *optstr;
- };
- #ifndef MS_LAZYTIME
- #define MS_LAZYTIME (1 << 25)
- #endif
- static int parseOCImountopts(struct blob_attr *msg, unsigned long *mount_flags, unsigned long *propagation_flags, char **mount_data, int *error)
- {
- struct blob_attr *cur;
- int rem;
- unsigned long mf = 0;
- unsigned long pf = 0;
- char *tmp;
- struct list_head fsopts = LIST_HEAD_INIT(fsopts);
- size_t len = 0;
- struct mount_opt *opt, *tmpopt;
- blobmsg_for_each_attr(cur, msg, rem) {
- tmp = blobmsg_get_string(cur);
- if (!strcmp("ro", tmp))
- mf |= MS_RDONLY;
- else if (!strcmp("rw", tmp))
- mf &= ~MS_RDONLY;
- else if (!strcmp("bind", tmp))
- mf = MS_BIND;
- else if (!strcmp("rbind", tmp))
- mf |= MS_BIND | MS_REC;
- else if (!strcmp("sync", tmp))
- mf |= MS_SYNCHRONOUS;
- else if (!strcmp("async", tmp))
- mf &= ~MS_SYNCHRONOUS;
- else if (!strcmp("atime", tmp))
- mf &= ~MS_NOATIME;
- else if (!strcmp("noatime", tmp))
- mf |= MS_NOATIME;
- else if (!strcmp("defaults", tmp))
- mf = 0; /* rw, suid, dev, exec, auto, nouser, and async */
- else if (!strcmp("dev", tmp))
- mf &= ~MS_NODEV;
- else if (!strcmp("nodev", tmp))
- mf |= MS_NODEV;
- else if (!strcmp("iversion", tmp))
- mf |= MS_I_VERSION;
- else if (!strcmp("noiversion", tmp))
- mf &= ~MS_I_VERSION;
- else if (!strcmp("diratime", tmp))
- mf &= ~MS_NODIRATIME;
- else if (!strcmp("nodiratime", tmp))
- mf |= MS_NODIRATIME;
- else if (!strcmp("dirsync", tmp))
- mf |= MS_DIRSYNC;
- else if (!strcmp("exec", tmp))
- mf &= ~MS_NOEXEC;
- else if (!strcmp("noexec", tmp))
- mf |= MS_NOEXEC;
- else if (!strcmp("mand", tmp))
- mf |= MS_MANDLOCK;
- else if (!strcmp("nomand", tmp))
- mf &= ~MS_MANDLOCK;
- else if (!strcmp("relatime", tmp))
- mf |= MS_RELATIME;
- else if (!strcmp("norelatime", tmp))
- mf &= ~MS_RELATIME;
- else if (!strcmp("strictatime", tmp))
- mf |= MS_STRICTATIME;
- else if (!strcmp("nostrictatime", tmp))
- mf &= ~MS_STRICTATIME;
- else if (!strcmp("lazytime", tmp))
- mf |= MS_LAZYTIME;
- else if (!strcmp("nolazytime", tmp))
- mf &= ~MS_LAZYTIME;
- else if (!strcmp("suid", tmp))
- mf &= ~MS_NOSUID;
- else if (!strcmp("nosuid", tmp))
- mf |= MS_NOSUID;
- else if (!strcmp("remount", tmp))
- mf |= MS_REMOUNT;
- /* propagation flags */
- else if (!strcmp("private", tmp))
- pf |= MS_PRIVATE;
- else if (!strcmp("rprivate", tmp))
- pf |= MS_PRIVATE | MS_REC;
- else if (!strcmp("slave", tmp))
- pf |= MS_SLAVE;
- else if (!strcmp("rslave", tmp))
- pf |= MS_SLAVE | MS_REC;
- else if (!strcmp("shared", tmp))
- pf |= MS_SHARED;
- else if (!strcmp("rshared", tmp))
- pf |= MS_SHARED | MS_REC;
- else if (!strcmp("unbindable", tmp))
- pf |= MS_UNBINDABLE;
- else if (!strcmp("runbindable", tmp))
- pf |= MS_UNBINDABLE | MS_REC;
- /* special case: 'nofail' */
- else if(!strcmp("nofail", tmp))
- *error = 0;
- else if (!strcmp("auto", tmp) ||
- !strcmp("noauto", tmp) ||
- !strcmp("user", tmp) ||
- !strcmp("group", tmp) ||
- !strcmp("_netdev", tmp))
- DEBUG("ignoring built-in mount option %s\n", tmp);
- else {
- /* filesystem-specific free-form option */
- opt = calloc(1, sizeof(*opt));
- opt->optstr = tmp;
- list_add_tail(&opt->list, &fsopts);
- }
- };
- *mount_flags = mf;
- *propagation_flags = pf;
- list_for_each_entry(opt, &fsopts, list) {
- if (len)
- ++len;
- len += strlen(opt->optstr);
- };
- if (len) {
- *mount_data = calloc(len + 1, sizeof(char));
- if (!(*mount_data))
- return ENOMEM;
- len = 0;
- list_for_each_entry(opt, &fsopts, list) {
- if (len)
- strcat(*mount_data, ",");
- strcat(*mount_data, opt->optstr);
- ++len;
- }
- list_for_each_entry_safe(opt, tmpopt, &fsopts, list) {
- list_del(&opt->list);
- free(opt);
- }
- }
- DEBUG("mount flags(%08lx) propagation(%08lx) fsopts(\"%s\")\n", mf, pf, *mount_data?:"");
- return 0;
- }
- int parseOCImount(struct blob_attr *msg)
- {
- struct blob_attr *tb[__OCI_MOUNT_MAX];
- unsigned long mount_flags = 0;
- unsigned long propagation_flags = 0;
- char *mount_data = NULL;
- int ret, err = -1;
- blobmsg_parse(oci_mount_policy, __OCI_MOUNT_MAX, tb, blobmsg_data(msg), blobmsg_len(msg));
- if (!tb[OCI_MOUNT_DESTINATION])
- return EINVAL;
- if (tb[OCI_MOUNT_OPTIONS]) {
- ret = parseOCImountopts(tb[OCI_MOUNT_OPTIONS], &mount_flags, &propagation_flags, &mount_data, &err);
- if (ret)
- return ret;
- }
- ret = add_mount(tb[OCI_MOUNT_SOURCE] ? blobmsg_get_string(tb[OCI_MOUNT_SOURCE]) : NULL,
- blobmsg_get_string(tb[OCI_MOUNT_DESTINATION]),
- tb[OCI_MOUNT_TYPE] ? blobmsg_get_string(tb[OCI_MOUNT_TYPE]) : NULL,
- mount_flags, propagation_flags, mount_data, err);
- if (mount_data)
- free(mount_data);
- return ret;
- }
- static void build_noafile(void) {
- int fd;
- fd = creat(UJAIL_NOAFILE, 0000);
- if (fd < 0)
- return;
- close(fd);
- return;
- }
- int mount_all(const char *jailroot) {
- struct library *l;
- struct mount *m;
- build_noafile();
- avl_for_each_element(&libraries, l, avl)
- add_mount_bind(l->path, 1, -1);
- avl_for_each_element(&mounts, m, avl)
- if (do_mount(jailroot, m->source, m->target, m->filesystemtype, m->mountflags,
- m->propflags, m->optstr, m->error, m->inner))
- return -1;
- return 0;
- }
- void mount_free(void) {
- struct mount *m, *tmp;
- avl_remove_all_elements(&mounts, m, avl, tmp) {
- if (m->source != (void*)(-1))
- free((void*)m->source);
- free((void*)m->target);
- free((void*)m->filesystemtype);
- free((void*)m->optstr);
- free(m);
- }
- }
- void mount_list_init(void) {
- avl_init(&mounts, avl_strcmp, false, NULL);
- }
- static int add_script_interp(const char *path, const char *map, int size)
- {
- int start = 2;
- while (start < size && map[start] != '/') {
- start++;
- }
- if (start >= size) {
- ERROR("bad script interp (%s)\n", path);
- return -1;
- }
- int stop = start + 1;
- while (stop < size && map[stop] > 0x20 && map[stop] <= 0x7e) {
- stop++;
- }
- if (stop >= size || (stop-start) > PATH_MAX) {
- ERROR("bad script interp (%s)\n", path);
- return -1;
- }
- char buf[PATH_MAX];
- strncpy(buf, map+start, stop-start);
- return add_path_and_deps(buf, 1, -1, 0);
- }
- int add_2paths_and_deps(const char *path, const char *path2, int readonly, int error, int lib)
- {
- assert(path != NULL);
- assert(path2 != NULL);
- if (lib == 0 && path[0] != '/') {
- ERROR("%s is not an absolute path\n", path);
- return error;
- }
- char *map = NULL;
- int fd, ret = -1;
- if (path[0] == '/') {
- if (avl_find(&mounts, path2))
- return 0;
- fd = open(path, O_RDONLY|O_CLOEXEC);
- if (fd < 0)
- return error;
- _add_mount_bind(path, path2, readonly, error);
- } else {
- if (avl_find(&libraries, path))
- return 0;
- char *fullpath;
- fd = lib_open(&fullpath, path);
- if (fd < 0)
- return error;
- if (fullpath) {
- alloc_library(fullpath, path);
- free(fullpath);
- }
- }
- struct stat s;
- if (fstat(fd, &s) == -1) {
- ERROR("fstat(%s) failed: %m\n", path);
- ret = error;
- goto out;
- }
- if (!S_ISREG(s.st_mode)) {
- ret = 0;
- goto out;
- }
- /* too small to be an ELF or a script -> "normal" file */
- if (s.st_size < 4) {
- ret = 0;
- goto out;
- }
- map = mmap(NULL, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
- if (map == MAP_FAILED) {
- ERROR("failed to mmap %s: %m\n", path);
- ret = -1;
- goto out;
- }
- if (map[0] == '#' && map[1] == '!') {
- ret = add_script_interp(path, map, s.st_size);
- goto out;
- }
- if (map[0] == ELFMAG0 && map[1] == ELFMAG1 && map[2] == ELFMAG2 && map[3] == ELFMAG3) {
- ret = elf_load_deps(path, map);
- goto out;
- }
- ret = 0;
- out:
- if (fd >= 0)
- close(fd);
- if (map)
- munmap(map, s.st_size);
- return ret;
- }
|