Cider Isn't Darwin Emulation, Really
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
#include <lkm/api.h>#include "vchroot_expand.h"#ifdef TEST# include <unistd.h># include <sys/stat.h># include <sys/syscall.h># include <stdio.h># include <stdbool.h># include <stdlib.h># include <string.h># include <errno.h># include <dirent.h># include <fcntl.h>
# 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 direnttypedef struct stat stat_t;#else# include "base.h"# include <mach/lkm.h># include <linux-syscalls/linux.h># include <fcntl/open.h># include "simple.h"# include <stdbool.h># include "errno.h"# include "duct_errno.h"# include "stat/common.h"# include "dirent/getdirentries.h"# include "common_at.h"#include <stddef.h>
#include <darlingserver/rpc.h>
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
#elsestatic 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 TESTVISIBLE#endifint __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 TESTVISIBLEint __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 TESTint main(int argc, const char** argv){ if (argc != 3) { printf("Usage: vchroot_expand <vchroot_dir> <path_to_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