diff --git a/src/kernel/emulation/linux/CMakeLists.txt b/src/kernel/emulation/linux/CMakeLists.txt index 18998ea4f..c32ca439f 100644 --- a/src/kernel/emulation/linux/CMakeLists.txt +++ b/src/kernel/emulation/linux/CMakeLists.txt @@ -277,7 +277,7 @@ set(emulation_sources psynch/ulock_wake.c conf/pathconf.c conf/fpathconf.c - vchroot_expand.c + vchroot_userspace.c syscalls-table.S linux-syscall.S ) diff --git a/src/kernel/emulation/linux/fdpath.c b/src/kernel/emulation/linux/fdpath.c index aa2e8927c..ff234b6c8 100644 --- a/src/kernel/emulation/linux/fdpath.c +++ b/src/kernel/emulation/linux/fdpath.c @@ -10,6 +10,7 @@ int fdpath(int fd, char* buf, size_t bufsiz) .maxlen = bufsiz }; - return lkm_call(NR_vchroot_fdpath, &args); + // return lkm_call(NR_vchroot_fdpath, &args); + return vchroot_fdpath(&args); } diff --git a/src/kernel/emulation/linux/vchroot_userspace.c b/src/kernel/emulation/linux/vchroot_userspace.c new file mode 100644 index 000000000..ce4644b0e --- /dev/null +++ b/src/kernel/emulation/linux/vchroot_userspace.c @@ -0,0 +1,434 @@ + +#include +#ifdef TEST +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include + +# define LINUX_SYSCALL(...) ({ int __rv = syscall(__VA_ARGS__); if (__rv == -1) __rv = -errno; __rv; }) +# define LINUX_ENOENT ENOENT +# define LINUX_ELOOP ELOOP +# define LINUX_ENAMETOOLONG ENAMETOOLONG + +# define LINUX_S_IFMT S_IFMT +# define LINUX_S_IFLNK S_IFLNK + +# define LINUX_O_RDONLY O_RDONLY + +# define __simple_sprintf sprintf +# define linux_dirent64 dirent +typedef struct stat stat_t; +#else +# include "base.h" +# include +# include +# include +# include "simple.h" +# include +# include "errno.h" +# include "duct_errno.h" +# include "stat/common.h" +# include "dirent/getdirentries.h" +typedef struct linux_stat stat_t; + +extern __SIZE_TYPE__ strlen(const char* s); +extern char* strcpy(char* dest, const char* src); +extern int strcmp(const char* str1, const char* str2); +extern int strncmp(const char* str1, const char* str2, __SIZE_TYPE__ n); +extern int strncasecmp(const char* str1, const char* str2, __SIZE_TYPE__ n); +extern int strcasecmp(const char* str1, const char* str2); +extern void *memcpy(void *dest, const void *src, __SIZE_TYPE__ n); + +#define LINUX_S_IFMT 00170000 +#define LINUX_S_IFLNK 0120000 + +#define __simple_printf(...) + +#endif + +static bool next_component(const char* from, const char** end); + +static char prefix_path[4096] = {0}; // MUST NOT end with '/' +static int prefix_path_len = -1; + +static const char EXIT_PATH[] = "/Volumes/SystemRoot"; +static bool icase_enabled = false; +static const int MAX_SYMLINK_DEPTH = 10; + +struct context +{ + const char* current_root; + int current_root_len; + + char current_path[4096]; + int current_path_len; + + int symlink_depth; + bool unknown_component; +}; + +static int vchroot_run(const char* path, struct context* ctxt); + +#ifndef TEST +VISIBLE +#endif +int __darling_vchroot(int dfd) +{ + int rv; + +#ifndef TEST + rv = lkm_call(NR_vchroot, dfd); + if (rv != 0) + return rv; +#endif + + char buf[50]; + + __simple_sprintf(buf, "/proc/self/fd/%d", dfd); + + rv = LINUX_SYSCALL(__NR_readlink, buf, prefix_path, sizeof(prefix_path) - 1); + prefix_path[rv] = '\0'; + prefix_path_len = rv; + + return 0; +} + +static void init_vchroot_path(void) +{ + struct vchroot_expand_args a = { + .path = "/", + .dfd = -100, + .flags = 0 + }; + + lkm_call(NR_vchroot_expand, &a); + + int len = strlen(a.path); + if (len > 0 && a.path[len-1] == '/') + a.path[--len] = '\0'; + + strcpy(prefix_path, a.path); + prefix_path_len = len; + +#ifndef TEST + __simple_printf("init_vchroot_path: %s\n", prefix_path); +#endif +} + +int vchroot_expand(struct vchroot_expand_args* args) +{ + struct context ctxt; + +#ifndef TEST + if (prefix_path_len == -1) + init_vchroot_path(); + __simple_printf("vchroot_expand(): input %s\n", args->path); +#endif + + ctxt.unknown_component = false; + ctxt.symlink_depth = 0; + ctxt.current_root = prefix_path; + ctxt.current_root_len = prefix_path_len; + + const char* input_path = args->path; + + if (*input_path != '/') + { + char buf[50]; + + if (args->dfd != -100) + __simple_sprintf(buf, "/proc/self/fd/%d", args->dfd); + else + strcpy(buf, "/proc/self/cwd"); + + int rv = LINUX_SYSCALL(__NR_readlink, buf, ctxt.current_path, sizeof(ctxt.current_path) - 1); + + if (rv < 0) + { +#ifndef TEST + __simple_printf(" dfd %d -> err %d!\n", args->dfd, rv); +#endif + return rv; + } + + ctxt.current_path_len = rv; + ctxt.current_path[rv] = '\0'; + } + + int rv = vchroot_run(input_path, &ctxt); + if (ctxt.current_path_len > 0) + strcpy(args->path, ctxt.current_path); + else + strcpy(args->path, "/"); + +#ifndef TEST + __simple_printf(" vchroot_expand(): rv=%d, expanded to %s\n", rv, args->path); +#endif + + return rv; +} + +static int vchroot_run(const char* input_path, struct context* ctxt) +{ + if (ctxt->symlink_depth > MAX_SYMLINK_DEPTH) + return -LINUX_ELOOP; + + if (*input_path == '/') + { + strcpy(ctxt->current_path, ctxt->current_root); + ctxt->current_path_len = strlen(ctxt->current_path); + input_path++; + } + + const char* end; + while (next_component(input_path, &end)) + { + const unsigned int len = end - input_path; + + if ((len == 1 && input_path[0] == '.') || len == 0) // . or nothing (//) + { + // nothing to do + } + else + { + // append / + ctxt->current_path[ctxt->current_path_len++] = '/'; + ctxt->current_path[ctxt->current_path_len] = '\0'; + + if (len == 2 && input_path[0] == '.' && input_path[1] == '.') // .. + { + if (ctxt->current_path_len <= 1) + return -LINUX_ENOENT; + + // current_path always ends with a slash + // p points before the last / + char* p = ctxt->current_path + ctxt->current_path_len - 2; + + while (true) + { + if (*p == '/') + { + *p = '\0'; + ctxt->current_path_len = p - ctxt->current_path; + + // We cannot exit current_root via .. + if (ctxt->current_path_len < ctxt->current_root_len) + return -LINUX_ENOENT; + break; + } + p--; + } + } + else + { + if (len + ctxt->current_path_len + 2 > sizeof(ctxt->current_path)) + return -LINUX_ENOENT; + + // Append the component + const int prevlen = ctxt->current_path_len; + + memcpy(ctxt->current_path + ctxt->current_path_len, input_path, len); + ctxt->current_path_len += len; + ctxt->current_path[ctxt->current_path_len] = '\0'; + + // Only analyze the last path component if we haven't had an unresolvent ENOENT before + if (!ctxt->unknown_component) + { + stat_t st; + int status = LINUX_SYSCALL(__NR_lstat, ctxt->current_path, &st); + + if (icase_enabled && status == -LINUX_ENOENT) + { + // Case insensitive search + + // Examine the directory above + ctxt->current_path[prevlen-1] = '\0'; + + int dfd = LINUX_SYSCALL(__NR_open, (prevlen > 1) ? ctxt->current_path : "/", LINUX_O_RDONLY); + char dirents[4096]; // buffer space for struct linux_dirent64 entries + int len; + + if (dfd < 0) + return dfd; + + // Get a batch of directory entries + while ((len = LINUX_SYSCALL(__NR_getdents64, dfd, dirents, sizeof(dirents))) > 0) + { + int pos = 0; + + // Iterate through directory entries + while (pos < len) + { + struct linux_dirent64* de = (struct linux_dirent64*) &dirents[pos]; + if (strcasecmp(de->d_name, ctxt->current_path + prevlen) == 0) + { + // Fix up the case and be done with it + strcpy(ctxt->current_path + prevlen, de->d_name); + goto done_getdents; + } + pos += de->d_reclen; + } + } + ctxt->unknown_component = true; +done_getdents: + // Restore the / we removed a few lines above + ctxt->current_path[prevlen-1] = '/'; + LINUX_SYSCALL(__NR_close, dfd); + } + else if ((st.st_mode & LINUX_S_IFMT) == LINUX_S_IFLNK) + { + char link[512]; + int rv; + + rv = LINUX_SYSCALL(__NR_readlink, ctxt->current_path, link, sizeof(link) - 1); + + if (rv < 0) + return rv; + + link[rv] = '\0'; + + // Remove the last component (because it will be substituted with symlink contents) + if (link[0] != '/') // Only bother to do that if we know that the symlink is not absolute + { + ctxt->current_path_len = prevlen - 1; // kill the last slash as well + ctxt->current_path[ctxt->current_path_len] = '\0'; + } + + // Symbolic link resolution + ctxt->symlink_depth++; + rv = vchroot_run(link, ctxt); + ctxt->symlink_depth--; + + if (rv != 0) + return rv; + } + } + + if (ctxt->current_root_len > 0 && ctxt->current_path_len - ctxt->current_root_len == sizeof(EXIT_PATH)-1) + { + if (strncasecmp(ctxt->current_path + ctxt->current_root_len, EXIT_PATH, sizeof(EXIT_PATH) - 1) == 0) + { + // Switch to the real system root + ctxt->current_path[0] = '\0'; + ctxt->current_path_len = 0; + ctxt->current_root = ""; + ctxt->current_root_len = 0; + } + } + } + } + + // Move on to the next component + input_path = end; + while (*input_path == '/') + input_path++; + } + + return 0; +} + +static bool next_component(const char* from, const char** end) +{ + const char* pos = from; + while (*pos != '/' && *pos) + pos++; + *end = pos; + return pos != from; +} + +int vchroot_fdpath(struct vchroot_fdpath_args* args) +{ +#ifndef TEST + if (prefix_path_len == -1) + init_vchroot_path(); +#endif + + char buf[50]; + char link[4096]; + + __simple_sprintf(buf, "/proc/self/fd/%d", args->fd); + + int rv = LINUX_SYSCALL(__NR_readlink, buf, link, sizeof(link) - 1); + if (rv < 0) + return rv; + + link[rv] = '\0'; + + if (rv >= prefix_path_len && strncmp(link, prefix_path, prefix_path_len) == 0) + { + if (args->maxlen-1 < rv - prefix_path_len) + return -LINUX_ENAMETOOLONG; + strcpy(args->path, link + prefix_path_len); + + if (args->path[0] == '\0') + strcpy(args->path, "/"); + } + else + { + if (args->maxlen < sizeof(EXIT_PATH) + rv) + return -LINUX_ENAMETOOLONG; + + memcpy(args->path, EXIT_PATH, sizeof(EXIT_PATH) - 1); + memcpy(args->path + sizeof(EXIT_PATH) - 1, link, rv+1); + } + +#ifndef TEST + __simple_printf("fdpath %d -> %s\n", args->fd, args->path); +#endif + + return 0; +} + +#ifdef TEST +int main(int argc, const char** argv) +{ + if (argc != 3) + { + printf("Usage: vchroot_expand \n"); + return 1; + } + + int dfd = open(argv[1], O_RDONLY | O_DIRECTORY); + __darling_vchroot(dfd); + close(dfd); + + if (getenv("ICASE")) + icase_enabled = true; + + struct vchroot_expand_args args; + strcpy(args.path, argv[2]); + + int rv = vchroot_expand(&args); + printf("vchroot_expand() rv = %d\n", rv); + printf("-> path = %s\n", args.path); + + if (rv == 0) + { + int fd = open(args.path, O_RDONLY); + struct vchroot_fdpath_args fdpath_args; + char buf[4096]; + + fdpath_args.path = buf; + fdpath_args.maxlen = sizeof(buf); + fdpath_args.fd = fd; + + rv = vchroot_fdpath(&fdpath_args); + + printf("vchroot_fdpath() rv = %d\n", rv); + if (rv >= 0) + printf("-> path = %s\n", buf); + + close(fd); + } + + return 0; +} +#endif +