From 472aa08222783b4255b60a4dd7a10197711bf703 Mon Sep 17 00:00:00 2001 From: tanyifeng Date: Mon, 14 Jan 2019 20:12:06 +0800 Subject: [PATCH 024/131] isulad: support symlink in mount entry, and not permit mount to /proc Signed-off-by: LiFeng --- src/lxc/Makefile.am | 2 + src/lxc/conf.c | 108 ++++++++- src/lxc/path.c | 546 ++++++++++++++++++++++++++++++++++++++++++++ src/lxc/path.h | 70 ++++++ 4 files changed, 721 insertions(+), 5 deletions(-) create mode 100644 src/lxc/path.c create mode 100644 src/lxc/path.h diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am index 08e2fab6..f2928b7d 100644 --- a/src/lxc/Makefile.am +++ b/src/lxc/Makefile.am @@ -12,6 +12,7 @@ noinst_HEADERS = attach.h \ confile_utils.h \ criu.h \ error.h \ + path.h \ file_utils.h \ ../include/netns_ifaddrs.h \ initutils.h \ @@ -95,6 +96,7 @@ liblxc_la_SOURCES = af_unix.c af_unix.h \ commands_utils.c commands_utils.h \ conf.c conf.h \ confile.c confile.h \ + path.c path.h \ confile_utils.c confile_utils.h \ criu.c criu.h \ error.c error.h \ diff --git a/src/lxc/conf.c b/src/lxc/conf.c index 55d1e453..800573a8 100644 --- a/src/lxc/conf.c +++ b/src/lxc/conf.c @@ -77,6 +77,7 @@ #include "storage/overlay.h" #include "syscall_wrappers.h" #include "terminal.h" +#include "path.h" #include "utils.h" #ifdef MAJOR_IN_MKDEV @@ -2309,6 +2310,79 @@ static void cull_mntent_opt(struct mntent *mntent) } } +/* isulad: checkMountDestination checks to ensure that the mount destination is not over the top of /proc. + * dest is required to be an abs path and have any symlinks resolved before calling this function. */ +static int check_mount_destination(const char *rootfs, const char *dest) +{ + const char *invalid_destinations[] = { + "/proc", + NULL + }; + // White list, it should be sub directories of invalid destinations + const char *valid_destinations[] = { + // These entries can be bind mounted by files emulated by fuse, + // so commands like top, free displays stats in container. + "/proc/cpuinfo", + "/proc/diskstats", + "/proc/meminfo", + "/proc/stat", + "/proc/swaps", + "/proc/uptime", + "/proc/net/dev", + NULL + }; + const char **valid, **invalid; + + for(valid = valid_destinations; *valid != NULL; valid++) { + char *fullpath, *relpath; + const char *parts[3] = { + rootfs, + *valid, + NULL + }; + fullpath = lxc_string_join("/", parts, false); + if (!fullpath) { + ERROR("Out of memory"); + return -1; + } + relpath = path_relative(fullpath, dest); + free(fullpath); + if (!relpath) + return -1; + if (!strcmp(relpath, ".")) { + free(relpath); + return 0; + } + free(relpath); + } + + for(invalid = invalid_destinations; *invalid != NULL; invalid++) { + char *fullpath, *relpath; + const char *parts[3] = { + rootfs, + *invalid, + NULL + }; + fullpath = lxc_string_join("/", parts, false); + if (!fullpath) { + ERROR("Out of memory"); + return -1; + } + relpath = path_relative(fullpath, dest); + free(fullpath); + if (!relpath) + return -1; + if (!strcmp(relpath, ".") || strncmp(relpath, "..", 2)) { + ERROR("%s cannot be mounted because it is located inside %s", dest, *invalid); + free(relpath); + return -1; + } + free(relpath); + } + + return 0; +} + static int mount_entry_create_dir_file(const struct mntent *mntent, const char *path, const struct lxc_rootfs *rootfs, @@ -2370,7 +2444,8 @@ static inline int mount_entry_on_generic(struct mntent *mntent, unsigned long mntflags, pflags; char *mntdata; bool dev, optional, relative; - char *rootfs_path = NULL; + char *rootfs_path = NULL, *rpath = NULL; + const char *dest = path; optional = hasmntopt(mntent, "optional") != NULL; dev = hasmntopt(mntent, "dev") != NULL; @@ -2379,9 +2454,29 @@ static inline int mount_entry_on_generic(struct mntent *mntent, if (rootfs && rootfs->path) rootfs_path = rootfs->mount; - ret = mount_entry_create_dir_file(mntent, path, rootfs, lxc_name, - lxc_path); + // isulad: ensure that the destination of the bind mount is resolved of symlinks at mount time because + // any previous mounts can invalidate the next mount's destination. + // this can happen when a user specifies mounts within other mounts to cause breakouts or other + // evil stuff to try to escape the container's rootfs. + if (rootfs_path) { + rpath = follow_symlink_in_scope(path, rootfs_path); + if (!rpath) { + ERROR("Failed to get real path for '%s'", path); + return -1; + } + dest = rpath; + + ret = check_mount_destination(rootfs_path, dest); + if (ret) { + ERROR("Mount destination is invalid: '%s'", dest); + free(rpath); + return -1; + } + } + + ret = mount_entry_create_dir_file(mntent, dest, rootfs, lxc_name, lxc_path); if (ret < 0) { + free(rpath); if (optional) return 0; @@ -2390,13 +2485,16 @@ static inline int mount_entry_on_generic(struct mntent *mntent, cull_mntent_opt(mntent); ret = parse_mntopts(mntent->mnt_opts, &mntflags, &pflags, &mntdata); - if (ret < 0) + if (ret < 0) { + free(rpath); return -1; + } - ret = mount_entry(mntent->mnt_fsname, path, mntent->mnt_type, mntflags, + ret = mount_entry(mntent->mnt_fsname, dest, mntent->mnt_type, mntflags, pflags, mntdata, optional, dev, relative, rootfs_path); free(mntdata); + free(rpath); return ret; } diff --git a/src/lxc/path.c b/src/lxc/path.c new file mode 100644 index 00000000..e917dcb8 --- /dev/null +++ b/src/lxc/path.c @@ -0,0 +1,546 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "path.h" +#include "log.h" + +lxc_log_define(lxc_path_ui, lxc); + +#define ISSLASH(C) ((C) == '/') +#define IS_ABSOLUTE_FILE_NAME(F) (ISSLASH ((F)[0])) +#define IS_RELATIVE_FILE_NAME(F) (! IS_ABSOLUTE_FILE_NAME (F)) + +bool specify_current_dir(const char *path) +{ + char *basec = NULL, *bname = NULL; + bool res = false; + + basec = strdup(path); + if (!basec) { + ERROR("Out of memory"); + return false; + } + + bname = basename(basec); + res = !strcmp(bname, "."); + free(basec); + return res; +} + +bool has_traling_path_separator(const char *path) +{ + return path && strlen(path) && (path[strlen(path) - 1] == '/'); +} + +// PreserveTrailingDotOrSeparator returns the given cleaned path +// and appends a trailing `/.` or `/` if its corresponding original +// path ends with a trailing `/.` or `/`. If the cleaned +// path already ends in a `.` path segment, then another is not added. If the +// clean path already ends in a path separator, then another is not added. +char *preserve_trailing_dot_or_separator(const char *cleanedpath, + const char *originalpath) +{ + char *respath = NULL; + size_t len; + + len = strlen(cleanedpath) + 3; + respath = malloc(len); + if (!respath) { + ERROR("Out of memory"); + return NULL; + } + memset(respath, 0x00, len); + strcat(respath, cleanedpath); + + if (!specify_current_dir(cleanedpath) && specify_current_dir(originalpath)) { + if (!has_traling_path_separator(respath)) + strcat(respath, "/"); + strcat(respath, "."); + } + + if (!has_traling_path_separator(respath) && + has_traling_path_separator(originalpath)) + strcat(respath, "/"); + + return respath; +} + + +// Split splits path immediately following the final Separator, +// separating it into a directory and file name component. +// If there is no Separator in path, Split returns an empty dir +// and file set to path. +// The returned values have the property that path = dir+file. +bool filepath_split(const char *path, char **dir, char **base) +{ + ssize_t i; + size_t len; + + len = strlen(path); + i = len - 1; + while (i >= 0 && path[i] != '/') + i--; + + *dir = malloc(i + 2); + if (!*dir) { + ERROR("Out of memory"); + return false; + } + memcpy(*dir, path, i + 1); + *(*dir + i + 1) = '\0'; + + *base = strdup(path + i + 1); + if (!*base) { + ERROR("Out of memory"); + free(*dir); + *dir = NULL; + return false; + } + + return true; +} + +/* + * cleanpath is similar to realpath of glibc, but not expands symbolic links, + * and not check the existence of components of the path. + */ +char *cleanpath(const char *path, char *resolved) +{ + char *rpath, *dest; + const char *start, *end, *rpath_limit; + + if (path == NULL || path[0] == '\0') + return NULL; + + if (resolved == NULL) { + rpath = malloc(PATH_MAX); + if (rpath == NULL) { + ERROR("Out of memory"); + return NULL; + } + } else { + rpath = resolved; + } + rpath_limit = rpath + PATH_MAX; + + if (!IS_ABSOLUTE_FILE_NAME(path)) { + if (!getcwd(rpath, PATH_MAX)) { + ERROR("Failed to getcwd"); + rpath[0] = '\0'; + goto error; + } + dest = strchr(rpath, '\0'); + start = path; + } else { + dest = rpath; + *dest++ = '/'; + start = path; + } + + for (end = start; *start; start = end) { + /* Skip sequence of multiple path-separators. */ + while (ISSLASH(*start)) + ++start; + + /* Find end of path component. */ + for (end = start; *end && !ISSLASH(*end); ++end) + /* Nothing. */; + + if (end - start == 0) { + break; + } else if (end - start == 1 && start[0] == '.') { + /* nothing */; + } else if (end - start == 2 && start[0] == '.' && start[1] == '.') { + /* Back up to previous component, ignore if at root already. */ + if (dest > rpath + 1) + for (--dest; dest > rpath && !ISSLASH(dest[-1]); --dest) + continue; + } else { + size_t new_size; + + if (!ISSLASH(dest[-1])) + *dest++ = '/'; + + if (dest + (end - start) >= rpath_limit) { + long long dest_offset = dest - rpath; + char *new_rpath; + + if (resolved) { + printf("Path is to long"); + if (dest > rpath + 1) + dest--; + *dest = '\0'; + goto error; + } + + new_size = rpath_limit - rpath; + if (end - start + 1 > PATH_MAX) + new_size += end - start + 1; + else + new_size += PATH_MAX; + new_rpath = (char *) realloc(rpath, new_size); + if (new_rpath == NULL) { + ERROR("Out of memory"); + goto error; + } + rpath = new_rpath; + rpath_limit = rpath + new_size; + + dest = rpath + dest_offset; + } + + memcpy(dest, start, end - start); + dest += end - start; + *dest = '\0'; + } + } + if (dest > rpath + 1 && ISSLASH(dest[-1])) + --dest; + *dest = '\0'; + + return rpath; + +error: + if (resolved == NULL) + free(rpath); + return NULL; +} + +// evalSymlinksInScope will evaluate symlinks in `path` within a scope `root` and return +// a result guaranteed to be contained within the scope `root`, at the time of the call. +// Symlinks in `root` are not evaluated and left as-is. +// Errors encountered while attempting to evaluate symlinks in path will be returned. +// Non-existing paths are valid and do not constitute an error. +// `path` has to contain `root` as a prefix, or else an error will be returned. +// Trying to break out from `root` does not constitute an error. +// +// Example: +// If /foo/bar -> /outside, +// FollowSymlinkInScope("/foo/bar", "/foo") == "/foo/outside" instead of "/oustide" +char *eval_symlinks_in_scope(const char *fullpath, const char *rootpath) +{ + char resroot[PATH_MAX] = {0}, *root = NULL; + char *rpath, *dest, *prefix, *extra_buf = NULL; + const char *start, *end, *rpath_limit; + int num_links = 0; + size_t prefix_len; + + if (!fullpath || !rootpath) + return NULL; + + root = cleanpath(rootpath, resroot); + if (!root) { + ERROR("Failed to get cleaned path"); + return NULL; + } + + if (!strcmp(fullpath, root)) + return strdup(fullpath); + + if (!strstr(fullpath, root)) { + ERROR("Path '%s' is not in '%s'", fullpath, root); + return NULL; + } + + rpath = malloc(PATH_MAX); + if (rpath == NULL) { + ERROR("Out of memory"); + goto error; + return NULL; + } + rpath_limit = rpath + PATH_MAX; + + prefix = root; + prefix_len = strlen(prefix); + if (!strcmp(prefix, "/")) + prefix_len = 0; + + dest = rpath; + if (prefix_len) { + memcpy(rpath, prefix, prefix_len); + dest += prefix_len; + } + *dest++ = '/'; + start = fullpath + prefix_len; + + for (end = start; *start; start = end) { + struct stat st; + int n; + + /* Skip sequence of multiple path-separators. */ + while (ISSLASH(*start)) + ++start; + + /* Find end of path component. */ + for (end = start; *end && !ISSLASH(*end); ++end) + /* Nothing. */; + + if (end - start == 0) { + break; + } else if (end - start == 1 && start[0] == '.') { + /* nothing */; + } else if (end - start == 2 && start[0] == '.' && start[1] == '.') { + /* Back up to previous component, ignore if at root already. */ + if (dest > rpath + prefix_len + 1) + for (--dest; dest > rpath && !ISSLASH(dest[-1]); --dest) + continue; + } else { + size_t new_size; + + if (!ISSLASH(dest[-1])) + *dest++ = '/'; + + if (dest + (end - start) >= rpath_limit) { + long long dest_offset = dest - rpath; + char *new_rpath; + + new_size = rpath_limit - rpath; + if (end - start + 1 > PATH_MAX) + new_size += end - start + 1; + else + new_size += PATH_MAX; + new_rpath = (char *) realloc(rpath, new_size); + if (new_rpath == NULL) { + ERROR("Out of memory"); + goto error; + } + rpath = new_rpath; + rpath_limit = rpath + new_size; + + dest = rpath + dest_offset; + } + + memcpy(dest, start, end - start); + dest += end - start; + *dest = '\0'; + + if (lstat(rpath, &st) < 0) { + // if rpath does not exist, accept it + continue; + } + + if (S_ISLNK(st.st_mode)) { + char *buf; + size_t len; + + if (++num_links > MAXSYMLINKS) { + ERROR("Too many links in '%s'", fullpath); + goto error; + } + + buf = malloc(PATH_MAX); + if (!buf) { + ERROR("Out of memory"); + goto error; + } + + n = readlink(rpath, buf, PATH_MAX - 1); + if (n < 0) { + free(buf); + goto error; + } + buf[n] = '\0'; + + if (!extra_buf) { + extra_buf = malloc(PATH_MAX); + if (!extra_buf) { + ERROR("Out of memory"); + free(buf); + goto error; + } + } + + len = strlen(end); + if ((long int)(n + len) >= PATH_MAX) { + free(buf); + ERROR("Path is too long"); + goto error; + } + + /* Careful here, end may be a pointer into extra_buf... */ + memmove(&extra_buf[n], end, len + 1); + fullpath = end = memcpy(extra_buf, buf, n); + + if (IS_ABSOLUTE_FILE_NAME(buf)) { + if (prefix_len) + memcpy(rpath, prefix, prefix_len); + dest = rpath + prefix_len; + *dest++ = '/'; /* It's an absolute symlink */ + } else { + /* Back up to previous component, ignore if at root + already: */ + if (dest > rpath + prefix_len + 1) + for (--dest; dest > rpath && !ISSLASH(dest[-1]); --dest) + continue; + } + } + } + } + if (dest > rpath + prefix_len + 1 && ISSLASH(dest[-1])) + --dest; + *dest = '\0'; + + if (extra_buf) + free(extra_buf); + + return rpath; + +error: + if (extra_buf) + free(extra_buf); + free(rpath); + return NULL; +} + +// FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an +// absolute path. This function handles paths in a platform-agnostic manner. +char *follow_symlink_in_scope(const char *fullpath, const char *rootpath) +{ + char resfull[PATH_MAX] = {0}, *full = NULL; + char resroot[PATH_MAX] = {0}, *root = NULL; + + full = cleanpath(fullpath, resfull); + if (!full) { + ERROR("Failed to get cleaned path"); + return NULL; + } + + root = cleanpath(rootpath, resroot); + if (!root) { + ERROR("Failed to get cleaned path"); + return NULL; + } + + return eval_symlinks_in_scope(full, root); +} + +// GetResourcePath evaluates `path` in the scope of the container's rootpath, with proper path +// sanitisation. Symlinks are all scoped to the rootpath of the container, as +// though the container's rootpath was `/`. +// +// The BaseFS of a container is the host-facing path which is bind-mounted as +// `/` inside the container. This method is essentially used to access a +// particular path inside the container as though you were a process in that +// container. +int get_resource_path(const char *rootpath, const char *path, + char **scopepath) +{ + char resolved[PATH_MAX] = {0}, *cleanedpath = NULL; + char *fullpath = NULL; + size_t len; + + if (!rootpath || !path || !scopepath) + return -1; + + *scopepath = NULL; + + cleanedpath = cleanpath(path, resolved); + if (!cleanedpath) { + ERROR("Failed to get cleaned path"); + return -1; + } + + len = strlen(rootpath) + strlen(cleanedpath) + 1; + fullpath = malloc(len); + if (!fullpath) { + ERROR("Out of memory"); + return -1; + } + snprintf(fullpath, len, "%s%s", rootpath, cleanedpath); + + *scopepath = follow_symlink_in_scope(fullpath, rootpath); + + free(fullpath); + return 0; +} + +// Rel returns a relative path that is lexically equivalent to targpath when +// joined to basepath with an intervening separator. That is, +// Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself. +// On success, the returned path will always be relative to basepath, +// even if basepath and targpath share no elements. +// An error is returned if targpath can't be made relative to basepath or if +// knowing the current working directory would be necessary to compute it. +// Rel calls Clean on the result. +char *path_relative(const char *basepath, const char *targpath) +{ + char resbase[PATH_MAX] = {0}, *base = NULL; + char restarg[PATH_MAX] = {0}, *targ = NULL; + size_t bl = 0, tl = 0, b0 = 0, bi = 0, t0 = 0, ti = 0; + + base = cleanpath(basepath, resbase); + if (!base) { + ERROR("Failed to get cleaned path"); + return NULL; + } + + targ = cleanpath(targpath, restarg); + if (!targ) { + ERROR("Failed to get cleaned path"); + return NULL; + } + + if (strcmp(base, targ) == 0) + return strdup("."); + + bl = strlen(base); + tl = strlen(targ); + while(true) { + while(bi < bl && !ISSLASH(base[bi])) + bi++; + while(ti < tl && !ISSLASH(targ[ti])) + ti++; + //not the same string + if (((bi - b0) != (ti - t0)) || strncmp(base + b0, targ + t0, bi - b0)) + break; + if (bi < bl) + bi++; + if (ti < tl) + ti++; + b0 = bi; + t0 = ti; + } + + if (b0 != bl) { + // Base elements left. Must go up before going down. + int seps = 0, i; + size_t ncopyed = 0, seps_size; + char *buf; + + for (bi = b0; bi < bl; bi++) { + if (ISSLASH(base[bi])) + seps++; + } + //strlen(..) + strlen(/..) + '\0' + seps_size = 2 + seps * 3 + 1; + if (t0 != tl) + seps_size += 1 + tl - t0; + + buf = calloc(seps_size, 1); + if (!buf) { + ERROR("Out of memory"); + return NULL; + } + buf[ncopyed++] = '.'; + buf[ncopyed++] = '.'; + for (i = 0; i < seps; i++) { + buf[ncopyed++] = '/'; + buf[ncopyed++] = '.'; + buf[ncopyed++] = '.'; + } + if (t0 != tl) { + buf[ncopyed++] = '/'; + memcpy(buf + ncopyed, targ + t0, tl - t0 + 1); + } + return buf; + } + + return strdup(targ + t0); +} \ No newline at end of file diff --git a/src/lxc/path.h b/src/lxc/path.h new file mode 100644 index 00000000..e3a04cc5 --- /dev/null +++ b/src/lxc/path.h @@ -0,0 +1,70 @@ +#ifndef __LCRD_PATH_H_ +#define __LCRD_PATH_H_ + +#include + +bool specify_current_dir(const char *path); + +bool has_traling_path_separator(const char *path); + +// PreserveTrailingDotOrSeparator returns the given cleaned path +// and appends a trailing `/.` or `/` if its corresponding original +// path ends with a trailing `/.` or `/`. If the cleaned +// path already ends in a `.` path segment, then another is not added. If the +// clean path already ends in a path separator, then another is not added. +char *preserve_trailing_dot_or_separator(const char *cleanedpath, + const char *originalpath); + + +// Split splits path immediately following the final Separator, +// separating it into a directory and file name component. +// If there is no Separator in path, Split returns an empty dir +// and file set to path. +// The returned values have the property that path = dir+file. +bool filepath_split(const char *path, char **dir, char **base); + +/* + * cleanpath is similar to realpath of glibc, but not expands symbolic links, + * and not check the existence of components of the path. + */ +char *cleanpath(const char *path, char *resolved); + +// evalSymlinksInScope will evaluate symlinks in `path` within a scope `root` and return +// a result guaranteed to be contained within the scope `root`, at the time of the call. +// Symlinks in `root` are not evaluated and left as-is. +// Errors encountered while attempting to evaluate symlinks in path will be returned. +// Non-existing paths are valid and do not constitute an error. +// `path` has to contain `root` as a prefix, or else an error will be returned. +// Trying to break out from `root` does not constitute an error. +// +// Example: +// If /foo/bar -> /outside, +// FollowSymlinkInScope("/foo/bar", "/foo") == "/foo/outside" instead of "/oustide" +char *eval_symlinks_in_scope(const char *fullpath, const char *rootpath); + +// FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an +// absolute path. This function handles paths in a platform-agnostic manner. +char *follow_symlink_in_scope(const char *fullpath, const char *rootpath); + +// GetResourcePath evaluates `path` in the scope of the container's rootpath, with proper path +// sanitisation. Symlinks are all scoped to the rootpath of the container, as +// though the container's rootpath was `/`. +// +// The BaseFS of a container is the host-facing path which is bind-mounted as +// `/` inside the container. This method is essentially used to access a +// particular path inside the container as though you were a process in that +// container. +int get_resource_path(const char *rootpath, const char *path, + char **scopepath); + +// Rel returns a relative path that is lexically equivalent to targpath when +// joined to basepath with an intervening separator. That is, +// Join(basepath, Rel(basepath, targpath)) is equivalent to targpath itself. +// On success, the returned path will always be relative to basepath, +// even if basepath and targpath share no elements. +// An error is returned if targpath can't be made relative to basepath or if +// knowing the current working directory would be necessary to compute it. +// Rel calls Clean on the result. +char *path_relative(const char *basepath, const char *targpath); + +#endif -- 2.23.0