#include #include "vchroot_expand.h" #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" # include "common_at.h" #include #include typedef struct linux_stat stat_t; struct _xlocale; typedef struct _xlocale* locale_t; #define LC_C_LOCALE ((locale_t)NULL) 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_l(const char* str1, const char* str2, __SIZE_TYPE__ n, locale_t locale); extern int strcasecmp(const char* str1, const char* str2); extern char* strcat(char* dest, const char* src); extern char *strchr(const char *s, int c); extern void *memcpy(void *dest, const void *src, __SIZE_TYPE__ n); extern void* memmove(void* dest, const void* src, __SIZE_TYPE__ n); extern int memcmp(const void* dest, const void* src, __SIZE_TYPE__ n); #define LINUX_S_IFMT 00170000 #define LINUX_S_IFLNK 0120000 #define __simple_printf(...) // #define __simple_printf __simple_kprintf // For bug hunting #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"; #ifndef TEST #ifdef VARIANT_DYLD #define icase_enabled false #else #define icase_enabled true #endif #else static bool icase_enabled = true; #endif 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; bool follow; }; static int vchroot_run(const char* path, struct context* ctxt); #ifndef TEST VISIBLE #endif int __darling_vchroot(int dfd) { int rv; #ifndef TEST rv = errno_linux_to_bsd(dserver_rpc_vchroot(dfd)); if (rv != 0) return rv; #endif char buf[50]; __simple_sprintf(buf, "/proc/self/fd/%d", dfd); #if defined(__NR_readlink) rv = LINUX_SYSCALL(__NR_readlink, buf, prefix_path, sizeof(prefix_path) - 1); #else rv = LINUX_SYSCALL(__NR_readlinkat, LINUX_AT_FDCWD, buf, prefix_path, sizeof(prefix_path) - 1); #endif prefix_path[rv] = '\0'; prefix_path_len = rv; return 0; } #ifndef TEST VISIBLE int __darling_vchroot_expand(const char* path, char* out) { struct vchroot_expand_args a = { .dfd = -100, .flags = 0 }; strcpy(a.path, path); int rv = vchroot_expand(&a); strcpy(out, a.path); return rv; } #endif static void init_vchroot_path(void) { uint64_t tmp_len; int code = dserver_rpc_vchroot_path(prefix_path, sizeof(prefix_path), &tmp_len); if (code < 0) { __simple_printf("Failed to get vchroot path: %d\n", code); __simple_abort(); } prefix_path_len = (int)tmp_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; ctxt.follow = !!(args->flags & VCHROOT_FOLLOW); const char* input_path = args->path; if (*input_path == '\0') return -LINUX_ENOENT; 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"); #if defined(__NR_readlink) int rv = LINUX_SYSCALL(__NR_readlink, buf, ctxt.current_path, sizeof(ctxt.current_path) - 1); #else int rv = LINUX_SYSCALL(__NR_readlinkat, LINUX_AT_FDCWD, buf, ctxt.current_path, sizeof(ctxt.current_path) - 1); #endif if (rv < 0) { #ifndef TEST __simple_printf(" dfd %d -> err %d!\n", args->dfd, rv); #endif return rv; } // If we got "/", then turn it into "", because our algorithm will add a trailing slash later if (rv == 1 && ctxt.current_path[0] == '/') { ctxt.current_path[0] = '\0'; ctxt.current_path_len = 0; } else { ctxt.current_path_len = rv; ctxt.current_path[rv] = '\0'; } // Are we outside the vchrooted area already? if (ctxt.current_path_len < ctxt.current_root_len || strncmp(ctxt.current_path, ctxt.current_root, ctxt.current_root_len) != 0) { ctxt.current_root = ""; ctxt.current_root_len = 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] == '.') // .. { // Returning back into the vchrooted area if (ctxt->current_path_len <= 1) { strcpy(ctxt->current_path, prefix_path); strcat(ctxt->current_path, EXIT_PATH); ctxt->current_path_len = prefix_path_len + sizeof(EXIT_PATH); ctxt->current_path[ctxt->current_path_len-1] = '/'; ctxt->current_path[ctxt->current_path_len] = '\0'; ctxt->current_root = prefix_path; ctxt->current_root_len = prefix_path_len; } // 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; #if defined(__NR_lstat) int status = LINUX_SYSCALL(__NR_lstat, ctxt->current_path, &st); #else #if __NR_newfstatat int status = LINUX_SYSCALL(__NR_newfstatat, LINUX_AT_FDCWD, ctxt->current_path, &st, LINUX_AT_SYMLINK_NOFOLLOW); #else int status = LINUX_SYSCALL(__NR_fstatat64, LINUX_AT_FDCWD, ctxt->current_path, &st, LINUX_AT_SYMLINK_NOFOLLOW); #endif #endif if (icase_enabled && status == -LINUX_ENOENT) { // Case insensitive search // Examine the directory above ctxt->current_path[prevlen-1] = '\0'; #if defined(__NR_open) int dfd = LINUX_SYSCALL(__NR_open, (prevlen > 1) ? ctxt->current_path : "/", LINUX_O_RDONLY); #else int dfd = LINUX_SYSCALL(__NR_openat, LINUX_AT_FDCWD, (prevlen > 1) ? ctxt->current_path : "/", LINUX_O_RDONLY); #endif char dirents[4096]; // buffer space for struct linux_dirent64 entries int len; if (dfd < 0) { ctxt->unknown_component = true; goto done_getdents; } // 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 (status == 0 && (st.st_mode & LINUX_S_IFMT) == LINUX_S_IFLNK) { // Follow symlink if follow is true or if we haven't reached the end of the input path yet if (ctxt->follow || *end) { char link[512]; int rv; #if defined(__NR_readlink) rv = LINUX_SYSCALL(__NR_readlink, ctxt->current_path, link, sizeof(link) - 1); #else rv = LINUX_SYSCALL(__NR_readlinkat, LINUX_AT_FDCWD, ctxt->current_path, link, sizeof(link) - 1); #endif if (rv < 0) return rv; link[rv] = '\0'; // https://github.com/darlinghq/darling/issues/916 // Special procfs hack due to the presence of broken symlinks in /proc/xxx/fd/ used to // point to sockets, pipes or anonymous inodes. if (strncmp(ctxt->current_path, "/proc", 5) == 0) { if (strchr(link, ':') != 0) { goto ignore_symlink; } } // 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 const bool orig_follow = ctxt->follow; ctxt->follow = true; ctxt->symlink_depth++; // resolve absolute symlinks within the prefix // // however, DON'T resolve `/proc` symlinks within the prefix. // anything under `/proc` is from Linux's `/proc` since we bind-mount it, so we can special-case them because we want those to be Linux-root relative. // see https://github.com/darlinghq/darling/issues/1052 for the issue this works around. if (link[0] == '/' && !(strncmp(link, "/proc/", 6) == 0 || (rv == 5 && strncmp(link, "/proc", 5) == 0))) { ctxt->current_root = prefix_path; ctxt->current_root_len = prefix_path_len; } rv = vchroot_run(link, ctxt); ctxt->symlink_depth--; ctxt->follow = orig_follow; if (rv != 0) return rv; } } } ignore_symlink: if (ctxt->current_root_len > 0 && ctxt->current_path_len - ctxt->current_root_len == sizeof(EXIT_PATH)-1) { if (strncasecmp_l(ctxt->current_path + ctxt->current_root_len, EXIT_PATH, sizeof(EXIT_PATH) - 1, LC_C_LOCALE) == 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 *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); #if defined(__NR_readlink) int rv = LINUX_SYSCALL(__NR_readlink, buf, link, sizeof(link) - 1); #else int rv = LINUX_SYSCALL(__NR_readlinkat, LINUX_AT_FDCWD, buf, link, sizeof(link) - 1); #endif 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; } int vchroot_unexpand(struct vchroot_unexpand_args* args) { if (memcmp(args->path, prefix_path, prefix_path_len) == 0) { int bytes = strlen(args->path + prefix_path_len) + 1; memmove(args->path, args->path + prefix_path_len, bytes); if (args->path[0] == '\0') strcpy(args->path, "/"); } else { int bytes = strlen(args->path) + 1; memmove(args->path + sizeof(EXIT_PATH) - 1, args->path, bytes); memcpy(args->path, EXIT_PATH, sizeof(EXIT_PATH) - 1); } 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; args.dfd = -100; 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