/*
This file is part of Darling.
Copyright (C) 2017 Lubos Dolezel
Darling is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Darling 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.
You should have received a copy of the GNU General Public License
along with Darling. If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "commpage.h"
#include "loader.h"
#include
#include
#include
#include
#include
#include
#include
#include
#ifndef PAGE_SIZE
# define PAGE_SIZE 4096
#endif
#define PAGE_ALIGN(x) (x & ~(PAGE_SIZE-1))
static const char* dyld_path = INSTALL_PREFIX "/libexec/usr/lib/dyld";
struct sockaddr_un __dserver_socket_address_data = {
.sun_family = AF_UNIX,
.sun_path = "\0",
};
int __dserver_main_thread_socket_fd = -1;
int __dserver_process_lifetime_pipe_fd = -1;
// The idea of mldr is to load dyld_path into memory and set up the stack
// as described in dyldStartup.S.
// After that, we pass control over to dyld.
//
// Additionally, mldr providers access to native platforms libdl.so APIs (ELF loader).
#ifdef __x86_64__
static void load64(int fd, bool expect_dylinker, struct load_results* lr);
static void reexec32(char** argv);
#endif
static void load32(int fd, bool expect_dylinker, struct load_results* lr);
static void load_fat(int fd, cpu_type_t cpu, bool expect_dylinker, char** argv, struct load_results* lr);
static void load(const char* path, cpu_type_t cpu, bool expect_dylinker, char** argv, struct load_results* lr);
static int native_prot(int prot);
static void setup_space(struct load_results* lr, bool is_64_bit);
static void process_special_env(struct load_results* lr);
static void start_thread(struct load_results* lr);
static bool is_kernel_at_least(int major, int minor);
static void* compatible_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
#ifdef __x86_64__
static void setup_stack64(const char* filepath, struct load_results* lr);
#endif
static void setup_stack32(const char* filepath, struct load_results* lr);
// this is called when argv[0] specifies an interpreter and we need to "unexpand" it (i.e. convert it from a Linux path to a vchrooted path)
static void vchroot_unexpand_interpreter(struct load_results* lr);
// UUID of the main executable
uint8_t exe_uuid[16];
// globally visible for debugging/core-dumping purposes
// however, this should not be relied on; a pointer to this should passed around to whoever needs the load_results structure
__attribute__((used))
struct load_results mldr_load_results = {0};
static uint32_t stack_size = 0;
static const char* const skip_env_vars[] = {
"__mldr_bprefs=",
"__mldr_sockpath=",
"__mldr_lifetime_pipe",
};
void* __mldr_main_stack_top = NULL;
static int kernel_major = -1;
static int kernel_minor = -1;
int main(int argc, char** argv, char** envp)
{
void** sp;
int pushCount = 0;
char *filename, *p = NULL;
size_t arg_strings_total_size_after = 0;
size_t orig_argv0_len = 0;
const char* orig_argv1 = NULL;
mldr_load_results.kernfd = -1;
mldr_load_results.argc = argc;
mldr_load_results.argv = argv;
while (envp[mldr_load_results.envc] != NULL) {
++mldr_load_results.envc;
}
mldr_load_results.envp = envp;
// sys_execve() passes the original file path appended to the mldr path in argv[0].
if (argc > 0)
p = strchr(argv[0], '!');
if (argc <= 1)
{
if (p == NULL) {
fprintf(stderr, "mldr is part of Darling. It is not to be executed directly.\n");
return 1;
}
else
{
fprintf(stderr, "mldr: warning: Executing with no argv[0]. Continuing anyway, but this is probably a bug.\n");
}
}
if (p != NULL)
{
filename = (char*) __builtin_alloca(strlen(argv[0])+1);
strcpy(filename, p + 1);
}
else
{
filename = (char*) __builtin_alloca(strlen(argv[1])+1);
strcpy(filename, argv[1]);
}
// allow any process to ptrace us
// the only process we really care about being able to do this is the server,
// but we can't just use the server's PID, since it lies outside our PID namespace.
ptrace(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0);
process_special_env(&mldr_load_results);
#ifdef __i386__
load(filename, CPU_TYPE_X86, false, argv, &mldr_load_results); // accept i386 only
#else
load(filename, 0, false, argv, &mldr_load_results);
#endif
// this was previously necessary when we were loading the binary from the LKM
// (presumably because the break was detected incorrectly)
// but this shouldn't be necessary for loading Mach-O's from userspace (the heap space should already be set up properly).
// see https://github.com/darlinghq/darling/issues/469 for the issue this originally fixed in the LKM
#if 0
if (prctl(PR_SET_MM, PR_SET_MM_BRK, PAGE_ALIGN(mldr_load_results.vm_addr_max), 0, 0) < 0) {
fprintf(stderr, "Failed to set BRK value\n");
return 1;
}
if (prctl(PR_SET_MM, PR_SET_MM_START_BRK, PAGE_ALIGN(mldr_load_results.vm_addr_max), 0, 0) < 0) {
fprintf(stderr, "Failed to set BRK start\n");
return 1;
}
#endif
// adjust argv (remove mldr's argv[0])
// NOTE: this code assumes that the current argv array points to contiguous strings.
// this is not necessarily true, although AFAIK this is always true on Linux.
// also note: we do it this way (moving the string contents in addition to the pointers)
// so that Linux sees our modified argv array without having to use PR_SET_MM_ARG_START
// and PR_SET_MM_ARG_END (since those require CAP_SYS_RESOURCE)
--mldr_load_results.argc;
orig_argv0_len = strlen(mldr_load_results.argv[0]) + 1;
orig_argv1 = mldr_load_results.argv[1];
for (size_t i = 0; i < mldr_load_results.argc; ++i) {
mldr_load_results.argv[i] = mldr_load_results.argv[0] + arg_strings_total_size_after;
arg_strings_total_size_after += strlen(mldr_load_results.argv[i + 1]) + 1;
}
mldr_load_results.argv[mldr_load_results.argc] = NULL;
memmove(mldr_load_results.argv[0], orig_argv1, arg_strings_total_size_after);
memset(mldr_load_results.argv[0] + arg_strings_total_size_after, 0, orig_argv0_len);
if (p == NULL) {
vchroot_unexpand_interpreter(&mldr_load_results);
}
// adjust envp (remove special mldr variables)
// NOTE: same as for argv; here we assume the envp strings are contiguous
for (size_t i = 0; i < mldr_load_results.envc; ++i) {
if (!mldr_load_results.envp[i]) {
mldr_load_results.envc = i;
break;
}
size_t len = strlen(mldr_load_results.envp[i]) + 1;
// Don't pass these special env vars down to userland
#define SKIP_VAR(_name) \
(len > sizeof(_name) - 1 && strncmp(mldr_load_results.envp[i], _name, sizeof(_name) - 1) == 0)
if (
SKIP_VAR("__mldr_bprefs=") ||
SKIP_VAR("__mldr_sockpath=")
) {
size_t len_after = 0;
const char* orig_envp_i_plus_one = mldr_load_results.envp[i + 1];
--mldr_load_results.envc;
for (size_t j = i; j < mldr_load_results.envc; ++j) {
mldr_load_results.envp[j] = mldr_load_results.envp[i] + len_after;
len_after += strlen(mldr_load_results.envp[j + 1]) + 1;
}
mldr_load_results.envp[mldr_load_results.envc] = NULL;
memmove(mldr_load_results.envp[i], orig_envp_i_plus_one, len_after);
memset(mldr_load_results.envp[i] + len_after, 0, len);
// we have to check this index again because it now points to a different string
--i;
continue;
}
}
if (mldr_load_results._32on64)
setup_stack32(filename, &mldr_load_results);
else
#ifdef __x86_64__
setup_stack64(filename, &mldr_load_results);
#elif __aarch64__
#error TODO: aarch64
#else
abort();
#endif
int status = dserver_rpc_set_dyld_info(mldr_load_results.dyld_all_image_location, mldr_load_results.dyld_all_image_size);
if (status < 0) {
fprintf(stderr, "Failed to tell darlingserver about our dyld info\n");
exit(1);
}
if (dserver_rpc_set_executable_path(filename, strlen(filename)) < 0) {
fprintf(stderr, "Failed to tell darlingserver about our executable path\n");
exit(1);
}
__mldr_main_stack_top = (void*)mldr_load_results.stack_top;
start_thread(&mldr_load_results);
__builtin_unreachable();
}
void load(const char* path, cpu_type_t forced_arch, bool expect_dylinker, char** argv, struct load_results* lr)
{
int fd;
uint32_t magic;
fd = open(path, O_RDONLY);
if (fd == -1)
{
fprintf(stderr, "Cannot open %s: %s\n", path, strerror(errno));
exit(1);
}
// We need to read argv[1] and detect whether it's a 32 or 64-bit application.
// Then load the appropriate version of dyld from the fat file.
// In case the to-be-executed executable contains both, we prefer the 64-bit version,
// unless a special property has been passed to sys_posix_spawn() to force the 32-bit
// version. See posix_spawnattr_setbinpref_np().
if (read(fd, &magic, sizeof(magic)) != sizeof(magic))
{
fprintf(stderr, "Cannot read the file header of %s.\n", path);
exit(1);
}
if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64)
{
#ifdef __x86_64__
lseek(fd, 0, SEEK_SET);
load64(fd, expect_dylinker, lr);
#else
abort();
#endif
}
else if (magic == MH_MAGIC || magic == MH_CIGAM)
{
#if !__x86_64__
lseek(fd, 0, SEEK_SET);
load32(fd, expect_dylinker, lr);
#else
// Re-run self as mldr32
reexec32(argv);
#endif
}
else if (magic == FAT_MAGIC || magic == FAT_CIGAM)
{
lseek(fd, 0, SEEK_SET);
load_fat(fd, forced_arch, expect_dylinker, argv, lr);
}
else
{
fprintf(stderr, "Unknown file format: %s.\n", path);
exit(1);
}
close(fd);
}
static void load_fat(int fd, cpu_type_t forced_arch, bool expect_dylinker, char** argv, struct load_results* lr) {
struct fat_header fhdr;
struct fat_arch best_arch = {0};
int bpref_index = -1;
best_arch.cputype = CPU_TYPE_ANY;
if (read(fd, &fhdr, sizeof(fhdr)) != sizeof(fhdr))
{
fprintf(stderr, "Cannot read fat file header.\n");
exit(1);
}
const bool swap = fhdr.magic == FAT_CIGAM;
#define SWAP32(x) x = __bswap_32(x)
if (swap)
SWAP32(fhdr.nfat_arch);
uint32_t i;
for (i = 0; i < fhdr.nfat_arch; i++)
{
struct fat_arch arch;
if (read(fd, &arch, sizeof(arch)) != sizeof(arch))
{
fprintf(stderr, "Cannot read fat_arch header.\n");
exit(1);
}
if (swap)
{
SWAP32(arch.cputype);
SWAP32(arch.cpusubtype);
SWAP32(arch.offset);
SWAP32(arch.size);
SWAP32(arch.align);
}
if (!forced_arch)
{
int j;
for (j = 0; j < 4; j++)
{
if (lr->bprefs[j] && arch.cputype == lr->bprefs[j])
{
if (bpref_index == -1 || bpref_index > j)
{
best_arch = arch;
bpref_index = j;
break;
}
}
}
if (bpref_index == -1)
{
#if defined(__x86_64__)
if (arch.cputype == CPU_TYPE_X86_64)
best_arch = arch;
else if (best_arch.cputype == CPU_TYPE_ANY && arch.cputype == CPU_TYPE_X86)
best_arch = arch;
#elif defined(__i386__)
if (arch.cputype == CPU_TYPE_X86)
best_arch = arch;
#elif defined (__aarch64__)
#error TODO: arm
#else
#error Unsupported CPU architecture
#endif
}
}
else
{
if (arch.cputype == forced_arch)
best_arch = arch;
}
}
if (best_arch.cputype == CPU_TYPE_ANY)
{
fprintf(stderr, "No supported architecture found in fat binary.\n");
exit(1);
}
if (lseek(fd, best_arch.offset, SEEK_SET) == -1)
{
fprintf(stderr, "Cannot seek to selected arch in fat binary.\n");
exit(1);
}
if (best_arch.cputype & CPU_ARCH_ABI64) {
#ifdef __x86_64__
load64(fd, expect_dylinker, lr);
#elif __aarch64__
#error TODO: aarch64
#else
abort();
#endif
} else {
#if !__x86_64__
load32(fd, expect_dylinker, lr);
#else
// Re-run self as mldr32
reexec32(argv);
#endif
}
};
#ifdef __x86_64__
#define GEN_64BIT
#include "loader.c"
#include "stack.c"
#undef GEN_64BIT
#endif
#define GEN_32BIT
#include "loader.c"
#include "stack.c"
#undef GEN_32BIT
int native_prot(int prot)
{
int protOut = 0;
if (prot & VM_PROT_READ)
protOut |= PROT_READ;
if (prot & VM_PROT_WRITE)
protOut |= PROT_WRITE;
if (prot & VM_PROT_EXECUTE)
protOut |= PROT_EXEC;
return protOut;
}
static void reexec32(char** argv)
{
char selfpath[1024];
ssize_t len;
len = readlink("/proc/self/exe", selfpath, sizeof(selfpath)-3);
if (len == -1)
{
perror("Cannot readlink /proc/self/exe");
abort();
}
selfpath[len] = '\0';
strcat(selfpath, "32");
execv(selfpath, argv);
perror("Cannot re-execute as 32-bit process");
abort();
}
// Given that there's no proper way of passing special parameters to the binary loader
// via execve(), we must do this via env variables
static void process_special_env(struct load_results* lr) {
const char* str;
static char root_path[4096];
lr->bprefs[0] = lr->bprefs[1] = lr->bprefs[2] = lr->bprefs[3] = 0;
str = getenv("__mldr_bprefs");
if (str != NULL) {
sscanf(str, "%x,%x,%x,%x", &lr->bprefs[0], &lr->bprefs[1], &lr->bprefs[2], &lr->bprefs[3]);
}
str = getenv("__mldr_sockpath");
if (str != NULL) {
if (strlen(str) > sizeof(__dserver_socket_address_data.sun_path) - 1) {
fprintf(stderr, "darlingserver socket path is too long\n");
exit(1);
}
strncpy(__dserver_socket_address_data.sun_path, str, sizeof(__dserver_socket_address_data.sun_path) - 1);
__dserver_socket_address_data.sun_path[sizeof(__dserver_socket_address_data.sun_path) - 1] = '\0';
lr->socket_path = __dserver_socket_address_data.sun_path;
}
lr->lifetime_pipe = -1;
str = getenv("__mldr_lifetime_pipe");
if (str != NULL) {
sscanf(str, "%i", &lr->lifetime_pipe);
}
str = getenv("DYLD_ROOT_PATH");
if (str != NULL && lr->root_path == NULL) {
strncpy(root_path, str, sizeof(root_path) - 1);
root_path[sizeof(root_path) - 1] = '\0';
lr->root_path = root_path;
lr->root_path_length = strlen(lr->root_path);
}
};
static void unset_special_env() {
unsetenv("__mldr_bprefs");
unsetenv("__mldr_sockpath");
unsetenv("__mldr_lifetime_pipe");
};
typedef struct socket_bitmap {
pthread_mutex_t mutex;
/**
* This is always next lowest available index.
* If this is equal to #bit_length, then the bitmap is full.
*/
size_t next_index;
uint8_t* bits;
size_t bit_length;
int highest;
} socket_bitmap_t;
static socket_bitmap_t socket_bitmap = {
.mutex = PTHREAD_MUTEX_INITIALIZER,
.next_index = 0,
.bits = NULL,
.bit_length = 0,
.highest = -1,
};
static int socket_bitmap_get(socket_bitmap_t* bitmap) {
int fd = -1;
bool updated = false;
pthread_mutex_lock(&bitmap->mutex);
if (bitmap->highest == -1) {
// we need to initialize this bitmap
struct rlimit limit;
if (getrlimit(RLIMIT_NOFILE, &limit) < 0) {
goto out;
}
if (limit.rlim_cur == RLIM_INFINITY) {
// just default to 1024
limit.rlim_cur = 1024;
}
bitmap->highest = limit.rlim_cur - 1;
}
if (bitmap->next_index >= bitmap->bit_length) {
// we need to grow the bitmap
if ((bitmap->bit_length % 8) == 0) {
// we need to allocate an additional byte
void* ptr = realloc(bitmap->bits, (bitmap->bit_length / 8) + 1);
if (!ptr) {
goto out;
}
bitmap->bits = ptr;
bitmap->bits[bitmap->bit_length / 8] = 0;
} else {
// we just need to increment the bit length
}
++bitmap->bit_length;
}
fd = bitmap->highest - bitmap->next_index;
bitmap->bits[bitmap->next_index / 8] |= 1 << (bitmap->next_index % 8);
// update the next available index
for (size_t i = bitmap->next_index + 1; i < bitmap->bit_length; ++i) {
size_t byte = i / 8;
uint8_t bit = i % 8;
if (bit == 0) {
// check the entire byte at once so we can avoid unnecessary iteration
if (bitmap->bits[byte] == 0xff) {
// this byte is full, skip it
i += 7;
continue;
}
}
if ((bitmap->bits[byte] & (1 << bit)) == 0) {
// this index is unused
bitmap->next_index = i;
updated = true;
break;
}
}
if (!updated) {
// all of our entries are currently in-use
bitmap->next_index = bitmap->bit_length;
}
out:
pthread_mutex_unlock(&bitmap->mutex);
return fd;
};
static void socket_bitmap_put(socket_bitmap_t* bitmap, int socket) {
size_t index;
pthread_mutex_lock(&bitmap->mutex);
index = bitmap->highest - socket;
bitmap->bits[index / 8] &= ~(1 << (index % 8));
if (index < bitmap->next_index) {
bitmap->next_index = index;
}
if (index == bitmap->bit_length - 1) {
// we can shrink the bitmap
size_t old_byte_size = (bitmap->bit_length + 7) / 8;
size_t new_byte_size = old_byte_size;
while (bitmap->bit_length > 0) {
size_t index = bitmap->bit_length - 1;
if ((bitmap->bit_length % 8) == 0) {
// check the entire byte at once to avoid unnecessary iteration
if (bitmap->bits[(bitmap->bit_length / 8) - 1] == 0) {
// remove this entire byte
bitmap->bit_length -= 8;
continue;
}
}
if ((bitmap->bits[index / 8] & (1 << (index % 8))) == 0) {
// this bit is in-use, so we can't shrink any further
break;
}
--bitmap->bit_length;
}
new_byte_size = (bitmap->bit_length + 7) / 8;
if (old_byte_size != new_byte_size) {
// we can free one or more bytes from the bitmap
void* ptr = realloc(bitmap->bits, new_byte_size);
if (!ptr) {
goto out;
}
bitmap->bits = ptr;
}
}
out:
pthread_mutex_unlock(&bitmap->mutex);
};
int __mldr_create_rpc_socket(void) {
int pre_fd = -1;
int fd = -1;
pre_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (pre_fd < 0) {
goto err_out;
}
fd = socket_bitmap_get(&socket_bitmap);
if (fd < 0) {
goto err_out;
}
if (dup2(pre_fd, fd) < 0) {
// we have to put it away ourselves here because `fd` is not yet valid, so we can't close() it in the error handler
socket_bitmap_put(&socket_bitmap, fd);
fd = -1;
goto err_out;
}
close(pre_fd);
pre_fd = -1;
// `fd` now contains the socket with the desired FD number returned by `socket_bitmap_get`
int fd_flags = fcntl(fd, F_GETFD);
if (fd_flags < 0) {
goto err_out;
}
if (fcntl(fd, F_SETFD, fd_flags | FD_CLOEXEC) < 0) {
goto err_out;
}
sa_family_t family = AF_UNIX;
if (bind(fd, (const struct sockaddr*)&family, sizeof(family)) < 0) {
goto err_out;
}
out:
return fd;
err_out:
if (fd >= 0) {
socket_bitmap_put(&socket_bitmap, fd);
close(fd);
}
if (pre_fd >= 0) {
close(pre_fd);
}
return -1;
};
void __mldr_close_rpc_socket(int socket) {
close(socket);
socket_bitmap_put(&socket_bitmap, socket);
};
int __mldr_create_process_lifetime_pipe(int* fds) {
// These pipes are not required for Linux 5.3 or newer,
// we already have pidfd_open.
if (is_kernel_at_least(5, 3)) {
fds[0] = fds[1] = -1;
return 0;
}
int pre_fds[2];
if (pipe(pre_fds) == -1) {
goto err_out;
}
for (int i = 0; i < 2; ++i) {
fds[i] = socket_bitmap_get(&socket_bitmap);
if (fds[i] < 0) {
goto err_out;
}
if (dup2(pre_fds[i], fds[i]) < 0) {
socket_bitmap_put(&socket_bitmap, fds[i]);
fds[i] = -1;
goto err_out;
}
close(pre_fds[i]);
pre_fds[i] = -1;
}
return 0;
err_out:
for (int i = 0; i < 2; ++i) {
if (fds[i] >= 0) {
socket_bitmap_put(&socket_bitmap, fds[i]);
close(fds[i]);
}
if (pre_fds[i] >= 0) {
close(pre_fds[i]);
}
}
return -1;
}
void __mldr_close_process_lifetime_pipe(int fd) {
if (fd != -1) {
close(fd);
socket_bitmap_put(&socket_bitmap, fd);
}
}
static void setup_space(struct load_results* lr, bool is_64_bit) {
commpage_setup(is_64_bit);
// Using the default stack top would cause the stack to be placed just above the commpage
// and would collide with it eventually.
// Instead, we manually allocate a new stack below the commpage.
#if __x86_64__
lr->stack_top = commpage_address(true);
#elif __i386__
lr->stack_top = commpage_address(false);
#else
#error Unsupported architecture
#endif
struct rlimit limit;
getrlimit(RLIMIT_STACK, &limit);
// allocate a few pages 16 pages if it's less than the limit; otherwise, allocate the limit
unsigned long size = PAGE_SIZE * 16;
if (limit.rlim_cur != RLIM_INFINITY && limit.rlim_cur < size) {
size = limit.rlim_cur;
}
if (compatible_mmap((void*)(lr->stack_top - size), size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE | MAP_GROWSDOWN, -1, 0) == MAP_FAILED) {
fprintf(stderr, "Failed to allocate stack of %lu bytes: %d (%s)\n", size, errno, strerror(errno));
exit(1);
}
unset_special_env();
lr->kernfd = __mldr_create_rpc_socket();
if (lr->kernfd < 0) {
fprintf(stderr, "Failed to create socket\n");
exit(1);
}
__dserver_main_thread_socket_fd = lr->kernfd;
int lifetime_pipe[2];
// this process is created using exec from another Darling process.
// darlingserver should already have the read pipe, so we don't need
// to check that in.
if (lr->lifetime_pipe != -1) {
lifetime_pipe[1] = socket_bitmap_get(&socket_bitmap);
if (lr->lifetime_pipe != lifetime_pipe[1]) {
// move the existing pipe to a higher fd number, and invalidate
// the old fd to prevent interfering with fds provided by
// socket_bitmap_get
if (dup2(lr->lifetime_pipe, lifetime_pipe[1]) == -1) {
fprintf(stderr, "Failed to dup process lifetime pipe: %d (%s)\n", errno, strerror(errno));
exit(1);
}
close(lr->lifetime_pipe);
}
lifetime_pipe[0] = -1;
} else {
if (__mldr_create_process_lifetime_pipe(lifetime_pipe) == -1) {
fprintf(stderr, "Failed to create process lifetime pipe: %d (%s)\n", errno, strerror(errno));
exit(1);
}
}
lr->lifetime_pipe = lifetime_pipe[1];
// store the write end of the pipe; the read end is sent to darlingserver.
__dserver_process_lifetime_pipe_fd = lifetime_pipe[1];
int dummy_stack_variable;
if (dserver_rpc_checkin(false, &dummy_stack_variable, lifetime_pipe[0]) < 0) {
fprintf(stderr, "Failed to checkin with darlingserver\n");
exit(1);
}
// keep our write end while closing the unused read end.
__mldr_close_process_lifetime_pipe(lifetime_pipe[0]);
if (!lr->root_path) {
static char vchroot_buffer[4096];
uint64_t vchroot_path_length = 0;
int code = dserver_rpc_vchroot_path(vchroot_buffer, sizeof(vchroot_buffer), &vchroot_path_length);
if (code < 0) {
fprintf(stderr, "Failed to retrieve vchroot path from darlingserver: %d\n", code);
exit(1);
}
if (vchroot_path_length >= sizeof(vchroot_buffer)) {
fprintf(stderr, "Vchroot path is too large for buffer\n");
exit(1);
} else if (vchroot_path_length > 0) {
lr->root_path = vchroot_buffer;
lr->root_path_length = vchroot_path_length;
}
}
};
static void start_thread(struct load_results* lr) {
#ifdef __x86_64__
__asm__ volatile(
"mov %1, %%rsp\n"
"jmpq *%0"
::
"m"(lr->entry_point),
"r"(lr->stack_top)
:
);
#elif defined(__i386__)
__asm__ volatile(
"mov %1, %%esp\n"
"jmp *%0"
::
"m"(lr->entry_point),
"r"(lr->stack_top)
:
);
#elif defined(__arm__)
__asm__ volatile(
"mov sp, %1\n"
"bx %0"
::
"r"(lr->entry_point),
"r"(lr->stack_top)
:
);
#else
# error Unsupported platform!
#endif
};
static bool is_kernel_at_least(int major, int minor) {
if (kernel_major == -1) {
struct utsname uname_info;
if (uname(&uname_info) == -1) {
return false;
}
kernel_major = 0;
kernel_minor = 0;
size_t pos = 0;
while (uname_info.release[pos] != '\0' && uname_info.release[pos] != '.') {
kernel_major = kernel_major * 10 + uname_info.release[pos] - '0';
++pos;
}
++pos;
while (uname_info.release[pos] != '\0' && uname_info.release[pos] != '.') {
kernel_minor = kernel_minor * 10 + uname_info.release[pos] - '0';
++pos;
}
}
if (major != kernel_major) {
return kernel_major > major;
}
return kernel_minor >= minor;
}
void* compatible_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) {
// MAP_FIXED_NOREPLACE is not supported on WSL1 (Linux < 4.17).
bool fixed_noreplace_hack = false;
if ((flags & MAP_FIXED_NOREPLACE) && !is_kernel_at_least(4, 17)) {
flags &= ~MAP_FIXED_NOREPLACE;
fixed_noreplace_hack = true;
}
void* result = mmap(addr, length, prot, flags, fd, offset);
// MAP_GROWSDOWN is not supported on WSL1. See https://github.com/microsoft/WSL/issues/8095.
if ((result == (void*)MAP_FAILED) && (flags & MAP_GROWSDOWN) && (errno == EOPNOTSUPP)) {
result = mmap(addr, length, prot, (flags & ~MAP_GROWSDOWN), fd, offset);
}
if (fixed_noreplace_hack) {
if (result != addr && result != (void*)MAP_FAILED) {
errno = ESRCH;
munmap(addr, length);
return MAP_FAILED;
}
}
return result;
}
static void vchroot_unexpand_interpreter(struct load_results* lr) {
static char unexpanded[4096];
size_t length;
if (lr->root_path) {
length = strlen(lr->argv[0]);
if (strncmp(lr->argv[0], lr->root_path, lr->root_path_length) == 0) {
memmove(unexpanded, lr->argv[0] + lr->root_path_length, length - lr->root_path_length + 1);
} else {
// FIXME: potential buffer overflow
memmove(unexpanded + sizeof(SYSTEM_ROOT) - 1, lr->argv[0], length + 1);
memcpy(unexpanded, SYSTEM_ROOT, sizeof(SYSTEM_ROOT) - 1);
}
lr->argv[0] = unexpanded;
}
};