2023-05-19 02:05:08 +00:00
|
|
|
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
|
|
│ vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi │
|
|
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
|
|
│ Copyright 2021 Justine Alexandra Roberts Tunney │
|
|
|
|
│ │
|
|
|
|
│ Permission to use, copy, modify, and/or distribute this software for │
|
|
|
|
│ any purpose with or without fee is hereby granted, provided that the │
|
|
|
|
│ above copyright notice and this permission notice appear in all copies. │
|
|
|
|
│ │
|
|
|
|
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
|
|
|
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
|
|
|
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
|
|
|
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
|
|
|
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
|
|
|
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
|
|
|
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
|
|
|
│ PERFORMANCE OF THIS SOFTWARE. │
|
|
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
2023-12-04 20:45:46 +00:00
|
|
|
#include <assert.h>
|
2023-06-04 08:57:10 +00:00
|
|
|
#include <dispatch/dispatch.h>
|
2023-11-05 09:36:47 +00:00
|
|
|
#include <dlfcn.h>
|
2023-05-19 02:05:08 +00:00
|
|
|
#include <errno.h>
|
2023-06-04 08:57:10 +00:00
|
|
|
#include <fcntl.h>
|
|
|
|
#include <libkern/OSCacheControl.h>
|
2023-05-19 02:05:08 +00:00
|
|
|
#include <limits.h>
|
|
|
|
#include <pthread.h>
|
2023-10-10 06:12:32 +00:00
|
|
|
#include <semaphore.h>
|
2023-06-04 08:57:10 +00:00
|
|
|
#include <signal.h>
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include <stdint.h>
|
2023-11-05 09:36:47 +00:00
|
|
|
#include <string.h>
|
2023-05-19 02:05:08 +00:00
|
|
|
#include <sys/mman.h>
|
2023-06-04 08:57:10 +00:00
|
|
|
#include <sys/random.h>
|
2024-05-03 06:21:43 +00:00
|
|
|
#include <sys/sysctl.h>
|
|
|
|
#include <sys/types.h>
|
2023-06-04 08:57:10 +00:00
|
|
|
#include <sys/uio.h>
|
|
|
|
#include <time.h>
|
|
|
|
#include <unistd.h>
|
2023-05-19 02:05:08 +00:00
|
|
|
|
2023-12-31 19:43:13 +00:00
|
|
|
#define pagesz 16384
|
2023-12-04 20:45:46 +00:00
|
|
|
/* maximum path size that cosmo can take */
|
|
|
|
#define PATHSIZE (PATH_MAX < 1024 ? PATH_MAX : 1024)
|
2023-06-04 08:57:10 +00:00
|
|
|
#define SYSLIB_MAGIC ('s' | 'l' << 8 | 'i' << 16 | 'b' << 24)
|
2024-05-03 06:21:43 +00:00
|
|
|
#define SYSLIB_VERSION 10 /* sync with libc/runtime/syslib.internal.h */
|
2023-05-19 02:05:08 +00:00
|
|
|
|
|
|
|
struct Syslib {
|
|
|
|
int magic;
|
|
|
|
int version;
|
|
|
|
long (*fork)(void);
|
|
|
|
long (*pipe)(int[2]);
|
|
|
|
long (*clock_gettime)(int, struct timespec *);
|
|
|
|
long (*nanosleep)(const struct timespec *, struct timespec *);
|
|
|
|
long (*mmap)(void *, size_t, int, int, int, off_t);
|
|
|
|
int (*pthread_jit_write_protect_supported_np)(void);
|
|
|
|
void (*pthread_jit_write_protect_np)(int);
|
|
|
|
void (*sys_icache_invalidate)(void *, size_t);
|
2023-06-04 08:57:10 +00:00
|
|
|
int (*pthread_create)(pthread_t *, const pthread_attr_t *, void *(*)(void *),
|
|
|
|
void *);
|
2023-05-19 02:05:08 +00:00
|
|
|
void (*pthread_exit)(void *);
|
|
|
|
int (*pthread_kill)(pthread_t, int);
|
2023-08-18 05:01:42 +00:00
|
|
|
int (*pthread_sigmask)(int, const sigset_t *, sigset_t *);
|
2023-05-19 02:05:08 +00:00
|
|
|
int (*pthread_setname_np)(const char *);
|
2023-06-04 08:57:10 +00:00
|
|
|
dispatch_semaphore_t (*dispatch_semaphore_create)(long);
|
|
|
|
long (*dispatch_semaphore_signal)(dispatch_semaphore_t);
|
|
|
|
long (*dispatch_semaphore_wait)(dispatch_semaphore_t, dispatch_time_t);
|
|
|
|
dispatch_time_t (*dispatch_walltime)(const struct timespec *, int64_t);
|
2023-09-12 04:34:53 +00:00
|
|
|
/* v2 (2023-09-10) */
|
|
|
|
pthread_t (*pthread_self)(void);
|
|
|
|
void (*dispatch_release)(dispatch_semaphore_t);
|
2023-11-12 06:32:12 +00:00
|
|
|
long (*raise)(int);
|
Make improvements
- Every unit test now passes on Apple Silicon. The final piece of this
puzzle was porting our POSIX threads cancelation support, since that
works differently on ARM64 XNU vs. AMD64. Our semaphore support on
Apple Silicon is also superior now compared to AMD64, thanks to the
grand central dispatch library which lets *NSYNC locks go faster.
- The Cosmopolitan runtime is now more stable, particularly on Windows.
To do this, thread local storage is mandatory at all runtime levels,
and the innermost packages of the C library is no longer being built
using ASAN. TLS is being bootstrapped with a 128-byte TIB during the
process startup phase, and then later on the runtime re-allocates it
either statically or dynamically to support code using _Thread_local.
fork() and execve() now do a better job cooperating with threads. We
can now check how much stack memory is left in the process or thread
when functions like kprintf() / execve() etc. call alloca(), so that
ENOMEM can be raised, reduce a buffer size, or just print a warning.
- POSIX signal emulation is now implemented the same way kernels do it
with pthread_kill() and raise(). Any thread can interrupt any other
thread, regardless of what it's doing. If it's blocked on read/write
then the killer thread will cancel its i/o operation so that EINTR can
be returned in the mark thread immediately. If it's doing a tight CPU
bound operation, then that's also interrupted by the signal delivery.
Signal delivery works now by suspending a thread and pushing context
data structures onto its stack, and redirecting its execution to a
trampoline function, which calls SetThreadContext(GetCurrentThread())
when it's done.
- We're now doing a better job managing locks and handles. On NetBSD we
now close semaphore file descriptors in forked children. Semaphores on
Windows can now be canceled immediately, which means mutexes/condition
variables will now go faster. Apple Silicon semaphores can be canceled
too. We're now using Apple's pthread_yield() funciton. Apple _nocancel
syscalls are now used on XNU when appropriate to ensure pthread_cancel
requests aren't lost. The MbedTLS library has been updated to support
POSIX thread cancelations. See tool/build/runitd.c for an example of
how it can be used for production multi-threaded tls servers. Handles
on Windows now leak less often across processes. All i/o operations on
Windows are now overlapped, which means file pointers can no longer be
inherited across dup() and fork() for the time being.
- We now spawn a thread on Windows to deliver SIGCHLD and wakeup wait4()
which means, for example, that posix_spawn() now goes 3x faster. POSIX
spawn is also now more correct. Like Musl, it's now able to report the
failure code of execve() via a pipe although our approach favors using
shared memory to do that on systems that have a true vfork() function.
- We now spawn a thread to deliver SIGALRM to threads when setitimer()
is used. This enables the most precise wakeups the OS makes possible.
- The Cosmopolitan runtime now uses less memory. On NetBSD for example,
it turned out the kernel would actually commit the PT_GNU_STACK size
which caused RSS to be 6mb for every process. Now it's down to ~4kb.
On Apple Silicon, we reduce the mandatory upstream thread size to the
smallest possible size to reduce the memory overhead of Cosmo threads.
The examples directory has a program called greenbean which can spawn
a web server on Linux with 10,000 worker threads and have the memory
usage of the process be ~77mb. The 1024 byte overhead of POSIX-style
thread-local storage is now optional; it won't be allocated until the
pthread_setspecific/getspecific functions are called. On Windows, the
threads that get spawned which are internal to the libc implementation
use reserve rather than commit memory, which shaves a few hundred kb.
- sigaltstack() is now supported on Windows, however it's currently not
able to be used to handle stack overflows, since crash signals are
still generated by WIN32. However the crash handler will still switch
to the alt stack, which is helpful in environments with tiny threads.
- Test binaries are now smaller. Many of the mandatory dependencies of
the test runner have been removed. This ensures many programs can do a
better job only linking the the thing they're testing. This caused the
test binaries for LIBC_FMT for example, to decrease from 200kb to 50kb
- long double is no longer used in the implementation details of libc,
except in the APIs that define it. The old code that used long double
for time (instead of struct timespec) has now been thoroughly removed.
- ShowCrashReports() is now much tinier in MODE=tiny. Instead of doing
backtraces itself, it'll just print a command you can run on the shell
using our new `cosmoaddr2line` program to view the backtrace.
- Crash report signal handling now works in a much better way. Instead
of terminating the process, it now relies on SA_RESETHAND so that the
default SIG_IGN behavior can terminate the process if necessary.
- Our pledge() functionality has now been fully ported to AARCH64 Linux.
2023-09-19 03:44:45 +00:00
|
|
|
int (*pthread_join)(pthread_t, void **);
|
|
|
|
void (*pthread_yield_np)(void);
|
|
|
|
int pthread_stack_min;
|
|
|
|
int sizeof_pthread_attr_t;
|
|
|
|
int (*pthread_attr_init)(pthread_attr_t *);
|
|
|
|
int (*pthread_attr_destroy)(pthread_attr_t *);
|
|
|
|
int (*pthread_attr_setstacksize)(pthread_attr_t *, size_t);
|
|
|
|
int (*pthread_attr_setguardsize)(pthread_attr_t *, size_t);
|
2023-09-21 14:30:39 +00:00
|
|
|
/* v4 (2023-09-19) */
|
|
|
|
void (*exit)(int);
|
|
|
|
long (*close)(int);
|
|
|
|
long (*munmap)(void *, size_t);
|
|
|
|
long (*openat)(int, const char *, int, int);
|
|
|
|
long (*write)(int, const void *, size_t);
|
|
|
|
long (*read)(int, void *, size_t);
|
|
|
|
long (*sigaction)(int, const struct sigaction *, struct sigaction *);
|
2023-10-03 02:25:19 +00:00
|
|
|
long (*pselect)(int, fd_set *, fd_set *, fd_set *, const struct timespec *,
|
|
|
|
const sigset_t *);
|
2023-09-21 14:30:39 +00:00
|
|
|
long (*mprotect)(void *, size_t, int);
|
2023-10-10 03:18:48 +00:00
|
|
|
/* v5 (2023-10-09) */
|
|
|
|
long (*sigaltstack)(const stack_t *, stack_t *);
|
|
|
|
long (*getentropy)(void *, size_t);
|
|
|
|
long (*sem_open)(const char *, int, uint16_t, unsigned);
|
|
|
|
long (*sem_unlink)(const char *);
|
|
|
|
long (*sem_close)(int *);
|
|
|
|
long (*sem_post)(int *);
|
|
|
|
long (*sem_wait)(int *);
|
|
|
|
long (*sem_trywait)(int *);
|
2023-10-10 06:12:32 +00:00
|
|
|
long (*getrlimit)(int, struct rlimit *);
|
|
|
|
long (*setrlimit)(int, const struct rlimit *);
|
2024-02-01 11:39:46 +00:00
|
|
|
/* v6 (2023-11-03) */
|
2023-11-03 13:04:13 +00:00
|
|
|
void *(*dlopen)(const char *, int);
|
|
|
|
void *(*dlsym)(void *, const char *);
|
|
|
|
int (*dlclose)(void *);
|
|
|
|
char *(*dlerror)(void);
|
2024-02-01 11:39:46 +00:00
|
|
|
/* MANDATORY (cosmo runtime won't load if version < 8)
|
|
|
|
---------------------------------------------------
|
|
|
|
OPTIONAL (cosmo lib should check __syslib->version) */
|
|
|
|
/* v9 (2024-01-31) */
|
|
|
|
int (*pthread_cpu_number_np)(size_t *);
|
2024-05-03 06:21:43 +00:00
|
|
|
/* v10 (2024-05-02) */
|
|
|
|
long (*sysctl)(int *, u_int, void *, size_t *, void *, size_t);
|
|
|
|
long (*sysctlbyname)(const char *, void *, size_t *, void *, size_t);
|
|
|
|
long (*sysctlnametomib)(const char *, int *, size_t *);
|
2023-05-19 02:05:08 +00:00
|
|
|
};
|
|
|
|
|
2024-01-07 15:13:20 +00:00
|
|
|
#define ELFCLASS32 1
|
|
|
|
#define ELFDATA2LSB 1
|
|
|
|
#define EM_AARCH64 183
|
|
|
|
#define ET_EXEC 2
|
|
|
|
#define ET_DYN 3
|
|
|
|
#define PT_LOAD 1
|
|
|
|
#define PT_DYNAMIC 2
|
|
|
|
#define PT_INTERP 3
|
|
|
|
#define EI_CLASS 4
|
|
|
|
#define EI_DATA 5
|
|
|
|
#define PF_X 1
|
|
|
|
#define PF_W 2
|
|
|
|
#define PF_R 4
|
|
|
|
#define AT_PHDR 3
|
|
|
|
#define AT_PHENT 4
|
|
|
|
#define AT_PHNUM 5
|
|
|
|
#define AT_PAGESZ 6
|
|
|
|
#define AT_BASE 7
|
|
|
|
#define AT_FLAGS 8
|
|
|
|
#define AT_FLAGS_PRESERVE_ARGV0_BIT 0
|
|
|
|
#define AT_FLAGS_PRESERVE_ARGV0 (1 << AT_FLAGS_PRESERVE_ARGV0_BIT)
|
|
|
|
#define AT_ENTRY 9
|
|
|
|
#define AT_UID 11
|
|
|
|
#define AT_EUID 12
|
|
|
|
#define AT_GID 13
|
|
|
|
#define AT_EGID 14
|
|
|
|
#define AT_HWCAP 16
|
|
|
|
#define AT_HWCAP2 16
|
|
|
|
#define AT_SECURE 23
|
|
|
|
#define AT_RANDOM 25
|
|
|
|
#define AT_EXECFN 31
|
|
|
|
|
2024-05-08 00:42:18 +00:00
|
|
|
#define EF_APE_MODERN 0x101ca75
|
|
|
|
#define EF_APE_MODERN_MASK 0x1ffffff
|
|
|
|
|
2024-01-07 15:13:20 +00:00
|
|
|
#define AUXV_WORDS 31
|
2023-05-19 02:05:08 +00:00
|
|
|
|
2023-08-17 14:21:13 +00:00
|
|
|
/* from the xnu codebase */
|
2023-05-19 02:05:08 +00:00
|
|
|
#define _COMM_PAGE_START_ADDRESS 0x0000000FFFFFC000ul
|
|
|
|
#define _COMM_PAGE_APRR_SUPPORT (_COMM_PAGE_START_ADDRESS + 0x10C)
|
|
|
|
#define _COMM_PAGE_APRR_WRITE_ENABLE (_COMM_PAGE_START_ADDRESS + 0x110)
|
|
|
|
#define _COMM_PAGE_APRR_WRITE_DISABLE (_COMM_PAGE_START_ADDRESS + 0x118)
|
|
|
|
|
2024-05-03 06:21:43 +00:00
|
|
|
#define Min(X, Y) ((Y) > (X) ? (X) : (Y))
|
|
|
|
#define Max(X, Y) ((Y) < (X) ? (X) : (Y))
|
2023-05-19 02:05:08 +00:00
|
|
|
|
2023-07-25 12:43:04 +00:00
|
|
|
#define READ32(S) \
|
2023-06-04 08:57:10 +00:00
|
|
|
((unsigned)(255 & (S)[3]) << 030 | (unsigned)(255 & (S)[2]) << 020 | \
|
|
|
|
(unsigned)(255 & (S)[1]) << 010 | (unsigned)(255 & (S)[0]) << 000)
|
2023-05-19 02:05:08 +00:00
|
|
|
|
2023-07-25 12:43:04 +00:00
|
|
|
#define READ64(S) \
|
2023-05-19 02:05:08 +00:00
|
|
|
((unsigned long)(255 & (S)[7]) << 070 | \
|
|
|
|
(unsigned long)(255 & (S)[6]) << 060 | \
|
|
|
|
(unsigned long)(255 & (S)[5]) << 050 | \
|
|
|
|
(unsigned long)(255 & (S)[4]) << 040 | \
|
|
|
|
(unsigned long)(255 & (S)[3]) << 030 | \
|
|
|
|
(unsigned long)(255 & (S)[2]) << 020 | \
|
|
|
|
(unsigned long)(255 & (S)[1]) << 010 | \
|
|
|
|
(unsigned long)(255 & (S)[0]) << 000)
|
|
|
|
|
|
|
|
struct ElfEhdr {
|
|
|
|
unsigned char e_ident[16];
|
|
|
|
unsigned short e_type;
|
|
|
|
unsigned short e_machine;
|
|
|
|
unsigned e_version;
|
|
|
|
unsigned long e_entry;
|
|
|
|
unsigned long e_phoff;
|
|
|
|
unsigned long e_shoff;
|
|
|
|
unsigned e_flags;
|
|
|
|
unsigned short e_ehsize;
|
|
|
|
unsigned short e_phentsize;
|
|
|
|
unsigned short e_phnum;
|
|
|
|
unsigned short e_shentsize;
|
|
|
|
unsigned short e_shnum;
|
|
|
|
unsigned short e_shstrndx;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct ElfPhdr {
|
|
|
|
unsigned p_type;
|
|
|
|
unsigned p_flags;
|
|
|
|
unsigned long p_offset;
|
|
|
|
unsigned long p_vaddr;
|
|
|
|
unsigned long p_paddr;
|
|
|
|
unsigned long p_filesz;
|
|
|
|
unsigned long p_memsz;
|
|
|
|
unsigned long p_align;
|
|
|
|
};
|
|
|
|
|
|
|
|
union ElfEhdrBuf {
|
|
|
|
struct ElfEhdr ehdr;
|
2023-07-25 12:43:04 +00:00
|
|
|
char buf[8192];
|
2023-05-19 02:05:08 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
union ElfPhdrBuf {
|
|
|
|
struct ElfPhdr phdr;
|
2023-08-17 14:21:13 +00:00
|
|
|
char buf[1024];
|
2023-05-19 02:05:08 +00:00
|
|
|
};
|
|
|
|
|
2023-06-04 08:57:10 +00:00
|
|
|
struct PathSearcher {
|
2023-09-11 20:51:37 +00:00
|
|
|
int literally;
|
$prog.ape support (#977)
* ape loader: $prog.ape + login shell support
If the ape loader is invoked with `$0 = $prog.ape`, then it searches for
a `$prog` in the same directory as it and loads that. In particular, the
loader searches the `PATH` for an executable named `$prog.ape`, then for
an executable named `$prog` in the same directory. If the former but not
the latter is found, the search terminates with an error.
It also handles the special case of getting started as `-$SHELL`, which
getty uses to indicate that the shell is a login shell. The path is not
searched in this case, and the program location is read straight out of
the `SHELL` variable.
It is now possible to have `/usr/local/bin/zsh.ape` act as a login shell
for a `/usr/local/bin/zsh` αpε, insofar as the program will get started
with the 'correct' args. Unfortunately, many things break if `$0` is not
the actual full path of the executable being run; for example, backspace
does not update the display properly.
To work around the brokenness introduced by not having `$0` be the full
path of the binary, we cut the leading `-` out of `argv[0]` if present.
This gets the loader's behavior with `$prog.ape` up to par, but doesn't
tell login shells that they are login shells.
So we introduce a hack to accomplish that: if ape is run as `-$prog.ape`
and the shell is `$prog`, the binary that is loaded has a `-l` flag put
into its first argument.
As of this commit, αpε binaries can be used as login shells on OSX.
* if islogin, execfn = shell
Prior to this, execfn was not being properly set for login shells that
did not receive `$_`, which was the case for iTerm2 on Mac. There were
no observable consequences of this, but fixing it seems good anyway.
* Fix auxv location calculation
In the non-login-shell case, it was leaving a word of uninitialized
memory at `envp[i] + 1`. This reuses the previous calculation based
on `envp`.
2023-12-04 03:39:32 +00:00
|
|
|
int indirect;
|
2023-06-04 08:57:10 +00:00
|
|
|
unsigned long namelen;
|
|
|
|
const char *name;
|
|
|
|
const char *syspath;
|
2023-12-04 20:45:46 +00:00
|
|
|
char path[PATHSIZE];
|
2023-06-04 08:57:10 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
struct ApeLoader {
|
|
|
|
struct PathSearcher ps;
|
|
|
|
union ElfPhdrBuf phdr;
|
|
|
|
struct Syslib lib;
|
|
|
|
char rando[16];
|
|
|
|
};
|
|
|
|
|
|
|
|
static unsigned long StrLen(const char *s) {
|
|
|
|
unsigned long n = 0;
|
|
|
|
while (*s++)
|
|
|
|
++n;
|
|
|
|
return n;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int StrCmp(const char *l, const char *r) {
|
|
|
|
unsigned long i = 0;
|
|
|
|
while (l[i] == r[i] && r[i])
|
|
|
|
++i;
|
|
|
|
return (l[i] & 255) - (r[i] & 255);
|
|
|
|
}
|
|
|
|
|
2023-07-26 20:54:49 +00:00
|
|
|
static const char *BaseName(const char *s) {
|
$prog.ape support (#977)
* ape loader: $prog.ape + login shell support
If the ape loader is invoked with `$0 = $prog.ape`, then it searches for
a `$prog` in the same directory as it and loads that. In particular, the
loader searches the `PATH` for an executable named `$prog.ape`, then for
an executable named `$prog` in the same directory. If the former but not
the latter is found, the search terminates with an error.
It also handles the special case of getting started as `-$SHELL`, which
getty uses to indicate that the shell is a login shell. The path is not
searched in this case, and the program location is read straight out of
the `SHELL` variable.
It is now possible to have `/usr/local/bin/zsh.ape` act as a login shell
for a `/usr/local/bin/zsh` αpε, insofar as the program will get started
with the 'correct' args. Unfortunately, many things break if `$0` is not
the actual full path of the executable being run; for example, backspace
does not update the display properly.
To work around the brokenness introduced by not having `$0` be the full
path of the binary, we cut the leading `-` out of `argv[0]` if present.
This gets the loader's behavior with `$prog.ape` up to par, but doesn't
tell login shells that they are login shells.
So we introduce a hack to accomplish that: if ape is run as `-$prog.ape`
and the shell is `$prog`, the binary that is loaded has a `-l` flag put
into its first argument.
As of this commit, αpε binaries can be used as login shells on OSX.
* if islogin, execfn = shell
Prior to this, execfn was not being properly set for login shells that
did not receive `$_`, which was the case for iTerm2 on Mac. There were
no observable consequences of this, but fixing it seems good anyway.
* Fix auxv location calculation
In the non-login-shell case, it was leaving a word of uninitialized
memory at `envp[i] + 1`. This reuses the previous calculation based
on `envp`.
2023-12-04 03:39:32 +00:00
|
|
|
const char *b = strrchr(s, '/');
|
|
|
|
return b ? b + 1 : s;
|
2023-07-26 20:54:49 +00:00
|
|
|
}
|
|
|
|
|
2023-05-19 02:05:08 +00:00
|
|
|
static char *GetEnv(char **p, const char *s) {
|
|
|
|
unsigned long i, j;
|
|
|
|
if (p) {
|
|
|
|
for (i = 0; p[i]; ++i) {
|
|
|
|
for (j = 0;; ++j) {
|
|
|
|
if (!s[j]) {
|
|
|
|
if (p[i][j] == '=') {
|
|
|
|
return p[i] + j + 1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (s[j] != p[i][j]) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *Utoa(char p[21], unsigned long x) {
|
|
|
|
char t;
|
|
|
|
unsigned long i, a, b;
|
|
|
|
i = 0;
|
|
|
|
do {
|
|
|
|
p[i++] = x % 10 + '0';
|
|
|
|
x = x / 10;
|
|
|
|
} while (x > 0);
|
|
|
|
p[i] = '\0';
|
|
|
|
if (i) {
|
|
|
|
for (a = 0, b = i - 1; a < b; ++a, --b) {
|
|
|
|
t = p[a];
|
|
|
|
p[a] = p[b];
|
|
|
|
p[b] = t;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return p + i;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *Itoa(char p[21], long x) {
|
|
|
|
if (x < 0)
|
|
|
|
*p++ = '-', x = -(unsigned long)x;
|
|
|
|
return Utoa(p, x);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void Emit(const char *s) {
|
|
|
|
write(2, s, StrLen(s));
|
|
|
|
}
|
|
|
|
|
2023-07-25 12:43:04 +00:00
|
|
|
static long Print(int fd, const char *s, ...) {
|
|
|
|
int c;
|
|
|
|
unsigned n;
|
|
|
|
char b[512];
|
|
|
|
__builtin_va_list va;
|
|
|
|
__builtin_va_start(va, s);
|
|
|
|
for (n = 0; s; s = __builtin_va_arg(va, const char *)) {
|
|
|
|
while ((c = *s++)) {
|
|
|
|
if (n < sizeof(b)) {
|
|
|
|
b[n++] = c;
|
|
|
|
}
|
|
|
|
}
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
2023-07-25 12:43:04 +00:00
|
|
|
__builtin_va_end(va);
|
|
|
|
return write(fd, b, n);
|
|
|
|
}
|
|
|
|
|
$prog.ape support (#977)
* ape loader: $prog.ape + login shell support
If the ape loader is invoked with `$0 = $prog.ape`, then it searches for
a `$prog` in the same directory as it and loads that. In particular, the
loader searches the `PATH` for an executable named `$prog.ape`, then for
an executable named `$prog` in the same directory. If the former but not
the latter is found, the search terminates with an error.
It also handles the special case of getting started as `-$SHELL`, which
getty uses to indicate that the shell is a login shell. The path is not
searched in this case, and the program location is read straight out of
the `SHELL` variable.
It is now possible to have `/usr/local/bin/zsh.ape` act as a login shell
for a `/usr/local/bin/zsh` αpε, insofar as the program will get started
with the 'correct' args. Unfortunately, many things break if `$0` is not
the actual full path of the executable being run; for example, backspace
does not update the display properly.
To work around the brokenness introduced by not having `$0` be the full
path of the binary, we cut the leading `-` out of `argv[0]` if present.
This gets the loader's behavior with `$prog.ape` up to par, but doesn't
tell login shells that they are login shells.
So we introduce a hack to accomplish that: if ape is run as `-$prog.ape`
and the shell is `$prog`, the binary that is loaded has a `-l` flag put
into its first argument.
As of this commit, αpε binaries can be used as login shells on OSX.
* if islogin, execfn = shell
Prior to this, execfn was not being properly set for login shells that
did not receive `$_`, which was the case for iTerm2 on Mac. There were
no observable consequences of this, but fixing it seems good anyway.
* Fix auxv location calculation
In the non-login-shell case, it was leaving a word of uninitialized
memory at `envp[i] + 1`. This reuses the previous calculation based
on `envp`.
2023-12-04 03:39:32 +00:00
|
|
|
static int GetIndirectOffset(const char *arg0) {
|
|
|
|
char *tail = strrchr(arg0, '.');
|
|
|
|
if (tail && !StrCmp(tail + 1, "ape")) {
|
|
|
|
return tail - arg0;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2023-07-25 12:43:04 +00:00
|
|
|
static void Perror(const char *thing, long rc, const char *reason) {
|
|
|
|
char ibuf[21];
|
|
|
|
ibuf[0] = 0;
|
|
|
|
if (rc)
|
|
|
|
Itoa(ibuf, -rc);
|
|
|
|
Print(2, "ape error: ", thing, ": ", reason, rc ? " failed w/ errno " : "",
|
|
|
|
ibuf, "\n", 0l);
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
|
|
|
|
2023-06-04 08:57:10 +00:00
|
|
|
__attribute__((__noreturn__)) static void Pexit(const char *c, int failed,
|
|
|
|
const char *s) {
|
2023-05-19 02:05:08 +00:00
|
|
|
Perror(c, failed, s);
|
|
|
|
_exit(127);
|
|
|
|
}
|
|
|
|
|
2023-10-03 02:25:19 +00:00
|
|
|
static char AccessCommand(struct PathSearcher *ps, unsigned long pathlen) {
|
Loader path security (#1012)
The ape loader now passes the program executable name directly as a
register. `x2` is used on aarch64, `%rdx` on x86_64. This is passed
as the third argument to `cosmo()` (M1) or `Launch` (non-M1) and is
assigned to the global `__program_executable_name`.
`GetProgramExecutableName` now returns this global's value, setting
it if it is initially null. `InitProgramExecutableName` first tries
exotic, secure methods: `KERN_PROC_PATHNAME` on FreeBSD/NetBSD, and
`/proc` on Linux. If those produce a reasonable response (i.e., not
`"/usr/bin/ape"`, which happens with the loader before this change),
that is used. Otherwise, if `issetugid()`, the empty string is used.
Otherwise, the old argv/envp parsing code is run.
The value returned from the loader is always the full absolute path
of the binary to be executed, having passed through `realpath`. For
the non-M1 loader, this necessitated writing `RealPath`, which uses
`readlinkat` of `"/proc/self/fd/[progfd]"` on Linux, `F_GETPATH` on
Xnu, and the `__realpath` syscall on OpenBSD. On FreeBSD/NetBSD, it
punts to `GetProgramExecutableName`, which is secure on those OSes.
With the loader, all platforms now have a secure program executable
name. With no loader or an old loader, everything still works as it
did, but setuid/setgid is not supported if the insecure pathfinding
code would have been needed.
Fixes #991.
2023-12-15 17:23:58 +00:00
|
|
|
if (pathlen + 1 + ps->namelen + 1 > sizeof(ps->path)) {
|
2023-12-04 20:45:46 +00:00
|
|
|
return 0;
|
|
|
|
}
|
2023-05-19 02:05:08 +00:00
|
|
|
if (pathlen && ps->path[pathlen - 1] != '/')
|
|
|
|
ps->path[pathlen++] = '/';
|
2023-11-05 09:36:47 +00:00
|
|
|
memmove(ps->path + pathlen, ps->name, ps->namelen);
|
2023-10-10 03:18:48 +00:00
|
|
|
ps->path[pathlen + ps->namelen] = 0;
|
2024-01-07 15:13:20 +00:00
|
|
|
return !access(ps->path, X_OK);
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
|
|
|
|
2023-10-03 02:25:19 +00:00
|
|
|
static char SearchPath(struct PathSearcher *ps) {
|
2023-05-19 02:05:08 +00:00
|
|
|
const char *p;
|
|
|
|
unsigned long i;
|
|
|
|
for (p = ps->syspath;;) {
|
|
|
|
for (i = 0; p[i] && p[i] != ':'; ++i) {
|
|
|
|
if (i < sizeof(ps->path)) {
|
|
|
|
ps->path[i] = p[i];
|
|
|
|
}
|
|
|
|
}
|
2023-10-03 02:25:19 +00:00
|
|
|
if (AccessCommand(ps, i)) {
|
2023-05-19 02:05:08 +00:00
|
|
|
return 1;
|
|
|
|
} else if (p[i] == ':') {
|
|
|
|
p += i + 1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-03 02:25:19 +00:00
|
|
|
static char FindCommand(struct PathSearcher *ps) {
|
2023-09-11 20:51:37 +00:00
|
|
|
ps->path[0] = 0;
|
|
|
|
|
|
|
|
/* paths are always 100% taken literally when a slash exists
|
2024-01-07 15:13:20 +00:00
|
|
|
$ ape foo/bar.com arg1 arg2
|
|
|
|
we don't run files in the current directory
|
2023-09-11 20:51:37 +00:00
|
|
|
$ ape foo.com arg1 arg2
|
|
|
|
unless $PATH has an empty string entry, e.g.
|
|
|
|
$ expert PATH=":/bin"
|
|
|
|
$ ape foo.com arg1 arg2
|
|
|
|
however we will execute this
|
|
|
|
$ ape - foo.com foo.com arg1 arg2
|
|
|
|
because cosmo's execve needs it */
|
2024-01-07 15:13:20 +00:00
|
|
|
if (ps->literally || memchr(ps->name, '/', ps->namelen)) {
|
|
|
|
return AccessCommand(ps, 0);
|
2023-09-11 20:51:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* otherwise search for name on $PATH */
|
2023-10-03 02:25:19 +00:00
|
|
|
return SearchPath(ps);
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
|
|
|
|
2023-06-04 08:57:10 +00:00
|
|
|
static char *Commandv(struct PathSearcher *ps, const char *name,
|
2023-05-19 02:05:08 +00:00
|
|
|
const char *syspath) {
|
|
|
|
ps->syspath = syspath ? syspath : "/bin:/usr/local/bin:/usr/bin";
|
2024-01-07 15:13:20 +00:00
|
|
|
ps->name = name;
|
|
|
|
if (!(ps->namelen = ps->indirect ? ps->indirect : StrLen(ps->name)))
|
|
|
|
return 0;
|
2023-05-19 02:05:08 +00:00
|
|
|
if (ps->namelen + 1 > sizeof(ps->path))
|
|
|
|
return 0;
|
2023-10-03 02:25:19 +00:00
|
|
|
if (FindCommand(ps)) {
|
2023-05-19 02:05:08 +00:00
|
|
|
return ps->path;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void pthread_jit_write_protect_np_workaround(int enabled) {
|
|
|
|
int count_start = 8192;
|
|
|
|
volatile int count = count_start;
|
2023-08-18 05:01:42 +00:00
|
|
|
unsigned long *addr, val, val2, reread = -1;
|
2023-05-19 02:05:08 +00:00
|
|
|
addr = (unsigned long *)(!enabled ? _COMM_PAGE_APRR_WRITE_ENABLE
|
2023-06-04 08:57:10 +00:00
|
|
|
: _COMM_PAGE_APRR_WRITE_DISABLE);
|
2023-05-19 02:05:08 +00:00
|
|
|
switch (*(volatile unsigned char *)_COMM_PAGE_APRR_SUPPORT) {
|
|
|
|
case 1:
|
|
|
|
do {
|
|
|
|
val = *addr;
|
|
|
|
reread = -1;
|
2023-08-18 05:01:42 +00:00
|
|
|
__asm__ volatile("msr\tS3_4_c15_c2_7,%0\n"
|
|
|
|
"isb\tsy\n"
|
|
|
|
: /* no outputs */
|
|
|
|
: "r"(val)
|
|
|
|
: "memory");
|
2023-05-19 02:05:08 +00:00
|
|
|
val2 = *addr;
|
2023-08-18 05:01:42 +00:00
|
|
|
__asm__ volatile("mrs\t%0,S3_4_c15_c2_7\n"
|
|
|
|
: "=r"(reread)
|
|
|
|
: /* no inputs */
|
|
|
|
: "memory");
|
2023-05-19 02:05:08 +00:00
|
|
|
if (val2 == reread) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
usleep(10);
|
|
|
|
} while (count-- > 0);
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
do {
|
|
|
|
val = *addr;
|
|
|
|
reread = -1;
|
2023-08-18 05:01:42 +00:00
|
|
|
__asm__ volatile("msr\tS3_6_c15_c1_5,%0\n"
|
|
|
|
"isb\tsy\n"
|
|
|
|
: /* no outputs */
|
|
|
|
: "r"(val)
|
|
|
|
: "memory");
|
2023-05-19 02:05:08 +00:00
|
|
|
val2 = *addr;
|
2023-08-18 05:01:42 +00:00
|
|
|
__asm__ volatile("mrs\t%0,S3_6_c15_c1_5\n"
|
|
|
|
: "=r"(reread)
|
|
|
|
: /* no inputs */
|
|
|
|
: "memory");
|
2023-05-19 02:05:08 +00:00
|
|
|
if (val2 == reread) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
usleep(10);
|
|
|
|
} while (count-- > 0);
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
pthread_jit_write_protect_np(enabled);
|
|
|
|
return;
|
|
|
|
}
|
2023-07-25 12:43:04 +00:00
|
|
|
Pexit("ape", 0, "failed to set jit write protection");
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
|
|
|
|
2023-11-05 09:36:47 +00:00
|
|
|
__attribute__((__noinline__)) static long sysret(long rc) {
|
|
|
|
return rc == -1 ? -errno : rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_fork(void) {
|
|
|
|
return sysret(fork());
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_close(int fd) {
|
|
|
|
return sysret(close(fd));
|
|
|
|
}
|
|
|
|
|
2023-11-12 06:32:12 +00:00
|
|
|
static long sys_raise(int sig) {
|
|
|
|
return sysret(raise(sig));
|
|
|
|
}
|
|
|
|
|
2023-11-05 09:36:47 +00:00
|
|
|
static long sys_pipe(int pfds[2]) {
|
|
|
|
return sysret(pipe(pfds));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_munmap(void *addr, size_t size) {
|
|
|
|
return sysret(munmap(addr, size));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_read(int fd, void *data, size_t size) {
|
|
|
|
return sysret(read(fd, data, size));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_mprotect(void *data, size_t size, int prot) {
|
|
|
|
return sysret(mprotect(data, size, prot));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_sigaltstack(const stack_t *ss, stack_t *oss) {
|
|
|
|
return sysret(sigaltstack(ss, oss));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_getentropy(void *buf, size_t buflen) {
|
|
|
|
return sysret(getentropy(buf, buflen));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_sem_open(const char *name, int oflags, mode_t mode,
|
|
|
|
unsigned value) {
|
|
|
|
return sysret((long)sem_open(name, oflags, mode, value));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_sem_unlink(const char *name) {
|
|
|
|
return sysret(sem_unlink(name));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_sem_close(sem_t *sem) {
|
|
|
|
return sysret(sem_close(sem));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_sem_post(sem_t *sem) {
|
|
|
|
return sysret(sem_post(sem));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_sem_wait(sem_t *sem) {
|
|
|
|
return sysret(sem_wait(sem));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_sem_trywait(sem_t *sem) {
|
|
|
|
return sysret(sem_trywait(sem));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_getrlimit(int which, struct rlimit *rlim) {
|
|
|
|
return sysret(getrlimit(which, rlim));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_setrlimit(int which, const struct rlimit *rlim) {
|
|
|
|
return sysret(setrlimit(which, rlim));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_write(int fd, const void *data, size_t size) {
|
|
|
|
return sysret(write(fd, data, size));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_clock_gettime(int clock, struct timespec *ts) {
|
|
|
|
return sysret(clock_gettime((clockid_t)clock, ts));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_openat(int fd, const char *path, int flags, int mode) {
|
|
|
|
return sysret(openat(fd, path, flags, mode));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_nanosleep(const struct timespec *req, struct timespec *rem) {
|
|
|
|
return sysret(nanosleep(req, rem));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_mmap(void *addr, size_t size, int prot, int flags, int fd,
|
|
|
|
off_t off) {
|
|
|
|
return sysret((long)mmap(addr, size, prot, flags, fd, off));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_sigaction(int sig, const struct sigaction *act,
|
|
|
|
struct sigaction *oact) {
|
|
|
|
return sysret(sigaction(sig, act, oact));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_pselect(int nfds, fd_set *readfds, fd_set *writefds,
|
|
|
|
fd_set *errorfds, const struct timespec *timeout,
|
|
|
|
const sigset_t *sigmask) {
|
|
|
|
return sysret(pselect(nfds, readfds, writefds, errorfds, timeout, sigmask));
|
|
|
|
}
|
|
|
|
|
2024-05-03 06:21:43 +00:00
|
|
|
static long sys_sysctl(int *name, u_int namelen, void *oldp, size_t *oldlenp,
|
|
|
|
void *newp, size_t newlen) {
|
|
|
|
return sysret(sysctl(name, namelen, oldp, oldlenp, newp, newlen));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_sysctlbyname(const char *name, void *oldp, size_t *oldlenp,
|
|
|
|
void *newp, size_t newlen) {
|
|
|
|
return sysret(sysctlbyname(name, oldp, oldlenp, newp, newlen));
|
|
|
|
}
|
|
|
|
|
|
|
|
static long sys_sysctlnametomib(const char *name, int *mibp, size_t *sizep) {
|
|
|
|
return sysret(sysctlnametomib(name, mibp, sizep));
|
|
|
|
}
|
|
|
|
|
2023-06-04 08:57:10 +00:00
|
|
|
__attribute__((__noreturn__)) static void Spawn(const char *exe, int fd,
|
|
|
|
long *sp, struct ElfEhdr *e,
|
|
|
|
struct ElfPhdr *p,
|
Loader path security (#1012)
The ape loader now passes the program executable name directly as a
register. `x2` is used on aarch64, `%rdx` on x86_64. This is passed
as the third argument to `cosmo()` (M1) or `Launch` (non-M1) and is
assigned to the global `__program_executable_name`.
`GetProgramExecutableName` now returns this global's value, setting
it if it is initially null. `InitProgramExecutableName` first tries
exotic, secure methods: `KERN_PROC_PATHNAME` on FreeBSD/NetBSD, and
`/proc` on Linux. If those produce a reasonable response (i.e., not
`"/usr/bin/ape"`, which happens with the loader before this change),
that is used. Otherwise, if `issetugid()`, the empty string is used.
Otherwise, the old argv/envp parsing code is run.
The value returned from the loader is always the full absolute path
of the binary to be executed, having passed through `realpath`. For
the non-M1 loader, this necessitated writing `RealPath`, which uses
`readlinkat` of `"/proc/self/fd/[progfd]"` on Linux, `F_GETPATH` on
Xnu, and the `__realpath` syscall on OpenBSD. On FreeBSD/NetBSD, it
punts to `GetProgramExecutableName`, which is secure on those OSes.
With the loader, all platforms now have a secure program executable
name. With no loader or an old loader, everything still works as it
did, but setuid/setgid is not supported if the insecure pathfinding
code would have been needed.
Fixes #991.
2023-12-15 17:23:58 +00:00
|
|
|
struct Syslib *lib,
|
|
|
|
char *path) {
|
2023-07-25 12:43:04 +00:00
|
|
|
long rc;
|
|
|
|
int prot;
|
|
|
|
int flags;
|
|
|
|
int found_entry;
|
|
|
|
unsigned long dynbase;
|
|
|
|
unsigned long virtmin, virtmax;
|
|
|
|
unsigned long a, b, c, d, i, j;
|
|
|
|
|
|
|
|
/* validate elf */
|
|
|
|
found_entry = 0;
|
|
|
|
virtmin = virtmax = 0;
|
|
|
|
for (i = 0; i < e->e_phnum; ++i) {
|
2023-05-19 02:05:08 +00:00
|
|
|
if (p[i].p_type != PT_LOAD) {
|
|
|
|
continue;
|
|
|
|
}
|
2023-07-25 12:43:04 +00:00
|
|
|
if (p[i].p_filesz > p[i].p_memsz) {
|
|
|
|
Pexit(exe, 0, "ELF p_filesz exceeds p_memsz");
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
|
|
|
if ((p[i].p_flags & (PF_W | PF_X)) == (PF_W | PF_X)) {
|
|
|
|
Pexit(exe, 0, "Apple Silicon doesn't allow RWX memory");
|
|
|
|
}
|
2023-07-25 12:43:04 +00:00
|
|
|
if ((p[i].p_vaddr & (pagesz - 1)) != (p[i].p_offset & (pagesz - 1))) {
|
|
|
|
Pexit(exe, 0, "ELF p_vaddr incongruent w/ p_offset modulo 16384");
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
2023-07-25 12:43:04 +00:00
|
|
|
if (p[i].p_vaddr + p[i].p_memsz < p[i].p_vaddr ||
|
|
|
|
p[i].p_vaddr + p[i].p_memsz + (pagesz - 1) < p[i].p_vaddr) {
|
|
|
|
Pexit(exe, 0, "ELF p_vaddr + p_memsz overflow");
|
|
|
|
}
|
|
|
|
if (p[i].p_offset + p[i].p_filesz < p[i].p_offset ||
|
|
|
|
p[i].p_offset + p[i].p_filesz + (pagesz - 1) < p[i].p_offset) {
|
|
|
|
Pexit(exe, 0, "ELF p_offset + p_filesz overflow");
|
|
|
|
}
|
|
|
|
a = p[i].p_vaddr & -pagesz;
|
|
|
|
b = (p[i].p_vaddr + p[i].p_memsz + (pagesz - 1)) & -pagesz;
|
|
|
|
for (j = i + 1; j < e->e_phnum; ++j) {
|
|
|
|
if (p[j].p_type != PT_LOAD)
|
|
|
|
continue;
|
|
|
|
c = p[j].p_vaddr & -pagesz;
|
|
|
|
d = (p[j].p_vaddr + p[j].p_memsz + (pagesz - 1)) & -pagesz;
|
2024-05-03 06:21:43 +00:00
|
|
|
if (Max(a, c) < Min(b, d)) {
|
2023-07-25 12:43:04 +00:00
|
|
|
Pexit(exe, 0, "ELF segments overlap each others virtual memory");
|
|
|
|
}
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
|
|
|
if (p[i].p_flags & PF_X) {
|
2023-07-25 12:43:04 +00:00
|
|
|
if (p[i].p_vaddr <= e->e_entry &&
|
|
|
|
e->e_entry < p[i].p_vaddr + p[i].p_memsz) {
|
|
|
|
found_entry = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (p[i].p_vaddr < virtmin) {
|
|
|
|
virtmin = p[i].p_vaddr;
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
2023-07-25 12:43:04 +00:00
|
|
|
if (p[i].p_vaddr + p[i].p_memsz > virtmax) {
|
|
|
|
virtmax = p[i].p_vaddr + p[i].p_memsz;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!found_entry) {
|
|
|
|
Pexit(exe, 0, "ELF entrypoint not found in PT_LOAD with PF_X");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* choose loading address for dynamic elf executables
|
|
|
|
that maintains relative distances between segments */
|
|
|
|
if (e->e_type == ET_DYN) {
|
2023-11-05 09:36:47 +00:00
|
|
|
rc = sys_mmap(0, virtmax - virtmin, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS,
|
|
|
|
-1, 0);
|
2023-07-25 12:43:04 +00:00
|
|
|
if (rc < 0)
|
|
|
|
Pexit(exe, rc, "pie mmap");
|
|
|
|
dynbase = rc;
|
|
|
|
if (dynbase & (pagesz - 1)) {
|
|
|
|
Pexit(exe, 0, "OS mmap incongruent w/ AT_PAGESZ");
|
|
|
|
}
|
|
|
|
if (dynbase + virtmin < dynbase) {
|
|
|
|
Pexit(exe, 0, "ELF dynamic base overflow");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
dynbase = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* load elf */
|
|
|
|
for (i = 0; i < e->e_phnum; ++i) {
|
2023-08-09 07:25:18 +00:00
|
|
|
void *addr;
|
|
|
|
unsigned long size;
|
2023-07-25 12:43:04 +00:00
|
|
|
if (p[i].p_type != PT_LOAD)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
/* configure mapping */
|
|
|
|
prot = 0;
|
|
|
|
flags = MAP_FIXED | MAP_PRIVATE;
|
|
|
|
if (p[i].p_flags & PF_R)
|
|
|
|
prot |= PROT_READ;
|
|
|
|
if (p[i].p_flags & PF_W)
|
|
|
|
prot |= PROT_WRITE;
|
|
|
|
if (p[i].p_flags & PF_X)
|
|
|
|
prot |= PROT_EXEC;
|
|
|
|
|
|
|
|
/* load from file */
|
2023-05-19 02:05:08 +00:00
|
|
|
if (p[i].p_filesz) {
|
2023-07-25 12:43:04 +00:00
|
|
|
int prot1, prot2;
|
2023-08-09 07:25:18 +00:00
|
|
|
unsigned long wipe;
|
2023-07-25 12:43:04 +00:00
|
|
|
prot1 = prot;
|
|
|
|
prot2 = prot;
|
2023-08-17 14:21:13 +00:00
|
|
|
/* when we ask the system to map the interval [vaddr,vaddr+filesz)
|
|
|
|
it might schlep extra file content into memory on both the left
|
|
|
|
and the righthand side. that's because elf doesn't require that
|
|
|
|
either side of the interval be aligned on the system page size.
|
|
|
|
normally we can get away with ignoring these junk bytes. but if
|
|
|
|
the segment defines bss memory (i.e. memsz > filesz) then we'll
|
|
|
|
need to clear the extra bytes in the page, if they exist. since
|
|
|
|
we can't do that if we're mapping a read-only page, we can just
|
|
|
|
map it with write permissions and call mprotect on it afterward */
|
2023-07-25 12:43:04 +00:00
|
|
|
a = p[i].p_vaddr + p[i].p_filesz; /* end of file content */
|
|
|
|
b = (a + (pagesz - 1)) & -pagesz; /* first pure bss page */
|
|
|
|
c = p[i].p_vaddr + p[i].p_memsz; /* end of segment data */
|
2024-05-03 06:21:43 +00:00
|
|
|
wipe = Min(b - a, c - a);
|
2023-08-09 07:25:18 +00:00
|
|
|
if (wipe && (~prot1 & PROT_WRITE)) {
|
2023-07-25 12:43:04 +00:00
|
|
|
prot1 = PROT_READ | PROT_WRITE;
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
2023-07-25 12:43:04 +00:00
|
|
|
addr = (void *)(dynbase + (p[i].p_vaddr & -pagesz));
|
|
|
|
size = (p[i].p_vaddr & (pagesz - 1)) + p[i].p_filesz;
|
2023-11-05 09:36:47 +00:00
|
|
|
if (prot1 & PROT_EXEC) {
|
2023-11-14 06:04:05 +00:00
|
|
|
#ifdef SIP_DISABLED
|
2024-02-01 11:39:46 +00:00
|
|
|
/* if sip is disabled then we can load the executable segments
|
|
|
|
off the binary into memory without needing to copy anything
|
|
|
|
which provides considerably better performance for building */
|
2023-11-05 09:36:47 +00:00
|
|
|
rc = sys_mmap(addr, size, prot1, flags, fd, p[i].p_offset & -pagesz);
|
|
|
|
if (rc < 0) {
|
2023-11-14 06:04:05 +00:00
|
|
|
if (rc == -EPERM) {
|
|
|
|
Pexit(exe, rc,
|
|
|
|
"map(exec) failed because SIP (System Integrity Protection) "
|
|
|
|
"is enabled, but APE Loader was compiled with SIP_DISABLED");
|
|
|
|
} else {
|
|
|
|
Pexit(exe, rc, "map(exec) failed");
|
|
|
|
}
|
2023-11-05 09:36:47 +00:00
|
|
|
}
|
2023-11-14 06:04:05 +00:00
|
|
|
#else
|
2024-02-01 11:39:46 +00:00
|
|
|
/* the issue is that if sip is enabled then, attempting to map
|
|
|
|
it with exec permission will cause xnu to phone home a hash
|
|
|
|
of the entire file to apple intelligence as a one time cost
|
|
|
|
which is literally minutes for executables holding big data
|
|
|
|
since there's no public apple api for detecting sip we read
|
|
|
|
as the default strategy which is slow but it works for both */
|
2023-11-14 06:04:05 +00:00
|
|
|
rc = sys_mmap(addr, size, (prot1 = PROT_READ | PROT_WRITE),
|
|
|
|
MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1, 0);
|
|
|
|
if (rc < 0)
|
|
|
|
Pexit(exe, rc, "prog mmap anon");
|
|
|
|
rc = pread(fd, addr, p[i].p_filesz, p[i].p_offset & -pagesz);
|
|
|
|
if (rc != p[i].p_filesz)
|
|
|
|
Pexit(exe, -errno, "prog pread");
|
|
|
|
#endif
|
2023-11-05 09:36:47 +00:00
|
|
|
} else {
|
|
|
|
rc = sys_mmap(addr, size, prot1, flags, fd, p[i].p_offset & -pagesz);
|
|
|
|
if (rc < 0)
|
|
|
|
Pexit(exe, rc, "prog mmap");
|
|
|
|
}
|
|
|
|
if (wipe)
|
|
|
|
memset((void *)(dynbase + a), 0, wipe);
|
2023-07-25 12:43:04 +00:00
|
|
|
if (prot2 != prot1) {
|
2023-11-05 09:36:47 +00:00
|
|
|
rc = sys_mprotect(addr, size, prot2);
|
2023-07-25 12:43:04 +00:00
|
|
|
if (rc < 0)
|
|
|
|
Pexit(exe, rc, "prog mprotect");
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
2023-08-09 07:25:18 +00:00
|
|
|
/* allocate extra bss */
|
|
|
|
if (c > b) {
|
|
|
|
flags |= MAP_ANONYMOUS;
|
2023-11-05 09:36:47 +00:00
|
|
|
rc = sys_mmap((void *)(dynbase + b), c - b, prot, flags, -1, 0);
|
2023-08-09 07:25:18 +00:00
|
|
|
if (rc < 0)
|
|
|
|
Pexit(exe, rc, "extra bss mmap");
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
/* allocate pure bss */
|
|
|
|
addr = (void *)(dynbase + (p[i].p_vaddr & -pagesz));
|
|
|
|
size = (p[i].p_vaddr & (pagesz - 1)) + p[i].p_memsz;
|
2023-07-25 12:43:04 +00:00
|
|
|
flags |= MAP_ANONYMOUS;
|
2023-11-05 09:36:47 +00:00
|
|
|
rc = sys_mmap(addr, size, prot, flags, -1, 0);
|
2023-07-25 12:43:04 +00:00
|
|
|
if (rc < 0)
|
|
|
|
Pexit(exe, rc, "bss mmap");
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-25 12:43:04 +00:00
|
|
|
/* finish up */
|
2023-05-19 02:05:08 +00:00
|
|
|
close(fd);
|
|
|
|
|
2023-08-18 05:01:42 +00:00
|
|
|
register long *x0 __asm__("x0") = sp;
|
Loader path security (#1012)
The ape loader now passes the program executable name directly as a
register. `x2` is used on aarch64, `%rdx` on x86_64. This is passed
as the third argument to `cosmo()` (M1) or `Launch` (non-M1) and is
assigned to the global `__program_executable_name`.
`GetProgramExecutableName` now returns this global's value, setting
it if it is initially null. `InitProgramExecutableName` first tries
exotic, secure methods: `KERN_PROC_PATHNAME` on FreeBSD/NetBSD, and
`/proc` on Linux. If those produce a reasonable response (i.e., not
`"/usr/bin/ape"`, which happens with the loader before this change),
that is used. Otherwise, if `issetugid()`, the empty string is used.
Otherwise, the old argv/envp parsing code is run.
The value returned from the loader is always the full absolute path
of the binary to be executed, having passed through `realpath`. For
the non-M1 loader, this necessitated writing `RealPath`, which uses
`readlinkat` of `"/proc/self/fd/[progfd]"` on Linux, `F_GETPATH` on
Xnu, and the `__realpath` syscall on OpenBSD. On FreeBSD/NetBSD, it
punts to `GetProgramExecutableName`, which is secure on those OSes.
With the loader, all platforms now have a secure program executable
name. With no loader or an old loader, everything still works as it
did, but setuid/setgid is not supported if the insecure pathfinding
code would have been needed.
Fixes #991.
2023-12-15 17:23:58 +00:00
|
|
|
register char *x2 __asm__("x2") = path;
|
2023-12-31 19:43:13 +00:00
|
|
|
register int x3 __asm__("x3") = 8; /* _HOSTXNU */
|
2023-08-18 05:01:42 +00:00
|
|
|
register struct Syslib *x15 __asm__("x15") = lib;
|
|
|
|
register long x16 __asm__("x16") = e->e_entry;
|
|
|
|
__asm__ volatile("mov\tx1,#0\n\t"
|
|
|
|
"mov\tx4,#0\n\t"
|
|
|
|
"mov\tx5,#0\n\t"
|
|
|
|
"mov\tx6,#0\n\t"
|
|
|
|
"mov\tx7,#0\n\t"
|
|
|
|
"mov\tx8,#0\n\t"
|
|
|
|
"mov\tx9,#0\n\t"
|
|
|
|
"mov\tx10,#0\n\t"
|
|
|
|
"mov\tx11,#0\n\t"
|
|
|
|
"mov\tx12,#0\n\t"
|
|
|
|
"mov\tx13,#0\n\t"
|
|
|
|
"mov\tx14,#0\n\t"
|
|
|
|
"mov\tx17,#0\n\t"
|
|
|
|
"mov\tx19,#0\n\t"
|
|
|
|
"mov\tx20,#0\n\t"
|
|
|
|
"mov\tx21,#0\n\t"
|
|
|
|
"mov\tx22,#0\n\t"
|
|
|
|
"mov\tx23,#0\n\t"
|
|
|
|
"mov\tx24,#0\n\t"
|
|
|
|
"mov\tx25,#0\n\t"
|
|
|
|
"mov\tx26,#0\n\t"
|
|
|
|
"mov\tx27,#0\n\t"
|
|
|
|
"mov\tx28,#0\n\t"
|
|
|
|
"mov\tx29,#0\n\t"
|
|
|
|
"mov\tx30,#0\n\t"
|
|
|
|
"mov\tsp,x0\n\t"
|
|
|
|
"mov\tx0,#0\n\t"
|
|
|
|
"br\tx16"
|
|
|
|
: /* no outputs */
|
2023-12-31 14:42:36 +00:00
|
|
|
: "r"(x0), "r"(x2), "r"(x3), "r"(x15), "r"(x16)
|
2023-08-18 05:01:42 +00:00
|
|
|
: "memory");
|
2023-05-19 02:05:08 +00:00
|
|
|
__builtin_unreachable();
|
|
|
|
}
|
|
|
|
|
2023-08-17 14:21:13 +00:00
|
|
|
static const char *TryElf(struct ApeLoader *M, union ElfEhdrBuf *ebuf,
|
2024-05-08 00:42:18 +00:00
|
|
|
char *exe, int fd, long *sp, long *auxv,
|
2023-08-17 14:21:13 +00:00
|
|
|
char *execfn) {
|
2023-07-25 12:43:04 +00:00
|
|
|
long i, rc;
|
2023-07-01 12:10:12 +00:00
|
|
|
unsigned size;
|
2023-07-25 12:43:04 +00:00
|
|
|
struct ElfEhdr *e;
|
|
|
|
struct ElfPhdr *p;
|
|
|
|
|
|
|
|
/* validate elf header */
|
2023-08-17 14:21:13 +00:00
|
|
|
e = &ebuf->ehdr;
|
|
|
|
if (READ32(ebuf->buf) != READ32("\177ELF")) {
|
2023-07-25 12:43:04 +00:00
|
|
|
return "didn't embed ELF magic";
|
|
|
|
}
|
|
|
|
if (e->e_ident[EI_CLASS] == ELFCLASS32) {
|
|
|
|
return "32-bit ELF isn't supported";
|
|
|
|
}
|
|
|
|
if (e->e_type != ET_EXEC && e->e_type != ET_DYN) {
|
|
|
|
return "ELF not ET_EXEC or ET_DYN";
|
|
|
|
}
|
|
|
|
if (e->e_machine != EM_AARCH64) {
|
|
|
|
return "couldn't find ELF header with ARM64 machine type";
|
|
|
|
}
|
2024-05-08 00:42:18 +00:00
|
|
|
if ((e->e_flags & EF_APE_MODERN_MASK) != EF_APE_MODERN && sp[0] > 0) {
|
|
|
|
/* change argv[0] to resolved path for older binaries */
|
|
|
|
((char **)(sp + 1))[0] = exe;
|
|
|
|
}
|
2023-07-25 12:43:04 +00:00
|
|
|
if (e->e_phentsize != sizeof(struct ElfPhdr)) {
|
|
|
|
Pexit(exe, 0, "e_phentsize is wrong");
|
|
|
|
}
|
|
|
|
size = e->e_phnum;
|
|
|
|
if ((size *= sizeof(struct ElfPhdr)) > sizeof(M->phdr.buf)) {
|
|
|
|
Pexit(exe, 0, "too many ELF program headers");
|
|
|
|
}
|
|
|
|
|
|
|
|
/* read program headers */
|
2023-08-17 14:21:13 +00:00
|
|
|
rc = pread(fd, M->phdr.buf, size, ebuf->ehdr.e_phoff);
|
2023-07-25 12:43:04 +00:00
|
|
|
if (rc < 0)
|
|
|
|
return "failed to read ELF program headers";
|
|
|
|
if (rc != size)
|
|
|
|
return "truncated read of ELF program headers";
|
|
|
|
|
|
|
|
/* bail on recoverable program header errors */
|
|
|
|
p = &M->phdr.phdr;
|
|
|
|
for (i = 0; i < e->e_phnum; ++i) {
|
|
|
|
if (p[i].p_type == PT_INTERP) {
|
|
|
|
return "ELF has PT_INTERP which isn't supported";
|
|
|
|
}
|
|
|
|
if (p[i].p_type == PT_DYNAMIC) {
|
|
|
|
return "ELF has PT_DYNAMIC which isn't supported";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* remove empty program headers */
|
|
|
|
for (i = 0; i < e->e_phnum;) {
|
|
|
|
if (p[i].p_type == PT_LOAD && !p[i].p_memsz) {
|
|
|
|
if (i + 1 < e->e_phnum) {
|
2023-11-05 09:36:47 +00:00
|
|
|
memmove(p + i, p + i + 1,
|
2023-07-25 12:43:04 +00:00
|
|
|
(e->e_phnum - (i + 1)) * sizeof(struct ElfPhdr));
|
|
|
|
}
|
|
|
|
--e->e_phnum;
|
|
|
|
} else {
|
|
|
|
++i;
|
|
|
|
}
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
2023-07-25 12:43:04 +00:00
|
|
|
|
2024-02-01 11:39:46 +00:00
|
|
|
/* merge adjacent loads that are contiguous with equal protection,
|
|
|
|
which prevents our program header overlap check from needlessly
|
|
|
|
failing later on; it also shaves away a microsecond of latency,
|
|
|
|
since every program header requires invoking at least 1 syscall */
|
2023-07-25 12:43:04 +00:00
|
|
|
for (i = 0; i + 1 < e->e_phnum;) {
|
|
|
|
if (p[i].p_type == PT_LOAD && p[i + 1].p_type == PT_LOAD &&
|
|
|
|
((p[i].p_flags & (PF_R | PF_W | PF_X)) ==
|
|
|
|
(p[i + 1].p_flags & (PF_R | PF_W | PF_X))) &&
|
|
|
|
((p[i].p_offset + p[i].p_filesz + (pagesz - 1)) & -pagesz) -
|
|
|
|
(p[i + 1].p_offset & -pagesz) <=
|
|
|
|
pagesz &&
|
|
|
|
((p[i].p_vaddr + p[i].p_memsz + (pagesz - 1)) & -pagesz) -
|
|
|
|
(p[i + 1].p_vaddr & -pagesz) <=
|
|
|
|
pagesz) {
|
|
|
|
p[i].p_memsz = (p[i + 1].p_vaddr + p[i + 1].p_memsz) - p[i].p_vaddr;
|
|
|
|
p[i].p_filesz = (p[i + 1].p_offset + p[i + 1].p_filesz) - p[i].p_offset;
|
|
|
|
if (i + 2 < e->e_phnum) {
|
2023-11-05 09:36:47 +00:00
|
|
|
memmove(p + i + 1, p + i + 2,
|
2023-07-25 12:43:04 +00:00
|
|
|
(e->e_phnum - (i + 2)) * sizeof(struct ElfPhdr));
|
|
|
|
}
|
|
|
|
--e->e_phnum;
|
|
|
|
} else {
|
|
|
|
++i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* simulate linux auxiliary values */
|
2023-08-17 14:21:13 +00:00
|
|
|
auxv[0] = AT_PHDR;
|
|
|
|
auxv[1] = (long)&M->phdr.phdr;
|
|
|
|
auxv[2] = AT_PHENT;
|
|
|
|
auxv[3] = ebuf->ehdr.e_phentsize;
|
|
|
|
auxv[4] = AT_PHNUM;
|
|
|
|
auxv[5] = ebuf->ehdr.e_phnum;
|
|
|
|
auxv[6] = AT_ENTRY;
|
|
|
|
auxv[7] = ebuf->ehdr.e_entry;
|
|
|
|
auxv[8] = AT_PAGESZ;
|
|
|
|
auxv[9] = pagesz;
|
2024-01-07 15:13:20 +00:00
|
|
|
auxv[10] = AT_FLAGS;
|
|
|
|
auxv[11] = M->ps.literally ? AT_FLAGS_PRESERVE_ARGV0 : 0;
|
|
|
|
auxv[12] = AT_UID;
|
|
|
|
auxv[13] = getuid();
|
|
|
|
auxv[14] = AT_EUID;
|
|
|
|
auxv[15] = geteuid();
|
|
|
|
auxv[16] = AT_GID;
|
|
|
|
auxv[17] = getgid();
|
|
|
|
auxv[18] = AT_EGID;
|
|
|
|
auxv[19] = getegid();
|
|
|
|
auxv[20] = AT_HWCAP;
|
|
|
|
auxv[21] = 0xffb3ffffu;
|
|
|
|
auxv[22] = AT_HWCAP2;
|
|
|
|
auxv[23] = 0x181;
|
|
|
|
auxv[24] = AT_SECURE;
|
|
|
|
auxv[25] = issetugid();
|
|
|
|
auxv[26] = AT_RANDOM;
|
|
|
|
auxv[27] = (long)M->rando;
|
|
|
|
auxv[28] = AT_EXECFN;
|
|
|
|
auxv[29] = (long)execfn;
|
|
|
|
auxv[30] = 0;
|
2023-07-25 12:43:04 +00:00
|
|
|
|
|
|
|
/* we're now ready to load */
|
Loader path security (#1012)
The ape loader now passes the program executable name directly as a
register. `x2` is used on aarch64, `%rdx` on x86_64. This is passed
as the third argument to `cosmo()` (M1) or `Launch` (non-M1) and is
assigned to the global `__program_executable_name`.
`GetProgramExecutableName` now returns this global's value, setting
it if it is initially null. `InitProgramExecutableName` first tries
exotic, secure methods: `KERN_PROC_PATHNAME` on FreeBSD/NetBSD, and
`/proc` on Linux. If those produce a reasonable response (i.e., not
`"/usr/bin/ape"`, which happens with the loader before this change),
that is used. Otherwise, if `issetugid()`, the empty string is used.
Otherwise, the old argv/envp parsing code is run.
The value returned from the loader is always the full absolute path
of the binary to be executed, having passed through `realpath`. For
the non-M1 loader, this necessitated writing `RealPath`, which uses
`readlinkat` of `"/proc/self/fd/[progfd]"` on Linux, `F_GETPATH` on
Xnu, and the `__realpath` syscall on OpenBSD. On FreeBSD/NetBSD, it
punts to `GetProgramExecutableName`, which is secure on those OSes.
With the loader, all platforms now have a secure program executable
name. With no loader or an old loader, everything still works as it
did, but setuid/setgid is not supported if the insecure pathfinding
code would have been needed.
Fixes #991.
2023-12-15 17:23:58 +00:00
|
|
|
Spawn(exe, fd, sp, e, p, &M->lib, M->ps.path);
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
|
|
|
|
2023-06-04 08:57:10 +00:00
|
|
|
int main(int argc, char **argv, char **envp) {
|
2023-08-18 05:01:42 +00:00
|
|
|
unsigned i;
|
2023-12-04 20:45:46 +00:00
|
|
|
int c, n, fd, rc;
|
2023-06-04 08:57:10 +00:00
|
|
|
struct ApeLoader *M;
|
2023-08-17 14:21:13 +00:00
|
|
|
long *sp, *sp2, *auxv;
|
|
|
|
union ElfEhdrBuf *ebuf;
|
2024-01-07 15:13:20 +00:00
|
|
|
char *p, *pe, *exe, *prog, *execfn;
|
2023-05-19 02:05:08 +00:00
|
|
|
|
2023-08-17 14:21:13 +00:00
|
|
|
/* allocate loader memory in program's arg block */
|
|
|
|
n = sizeof(struct ApeLoader);
|
2023-08-18 05:01:42 +00:00
|
|
|
M = (struct ApeLoader *)__builtin_alloca(n);
|
2023-06-04 08:57:10 +00:00
|
|
|
|
2023-08-17 14:21:13 +00:00
|
|
|
/* expose apple libs */
|
2023-06-04 08:57:10 +00:00
|
|
|
M->lib.magic = SYSLIB_MAGIC;
|
|
|
|
M->lib.version = SYSLIB_VERSION;
|
|
|
|
M->lib.fork = sys_fork;
|
|
|
|
M->lib.pipe = sys_pipe;
|
|
|
|
M->lib.clock_gettime = sys_clock_gettime;
|
|
|
|
M->lib.nanosleep = sys_nanosleep;
|
|
|
|
M->lib.mmap = sys_mmap;
|
|
|
|
M->lib.pthread_jit_write_protect_supported_np =
|
2023-05-19 02:05:08 +00:00
|
|
|
pthread_jit_write_protect_supported_np;
|
2023-06-04 08:57:10 +00:00
|
|
|
M->lib.pthread_jit_write_protect_np = pthread_jit_write_protect_np_workaround;
|
2023-11-17 10:33:14 +00:00
|
|
|
M->lib.sys_icache_invalidate = sys_icache_invalidate;
|
2023-06-04 08:57:10 +00:00
|
|
|
M->lib.pthread_create = pthread_create;
|
|
|
|
M->lib.pthread_exit = pthread_exit;
|
|
|
|
M->lib.pthread_kill = pthread_kill;
|
|
|
|
M->lib.pthread_sigmask = pthread_sigmask;
|
|
|
|
M->lib.pthread_setname_np = pthread_setname_np;
|
|
|
|
M->lib.dispatch_semaphore_create = dispatch_semaphore_create;
|
|
|
|
M->lib.dispatch_semaphore_signal = dispatch_semaphore_signal;
|
|
|
|
M->lib.dispatch_semaphore_wait = dispatch_semaphore_wait;
|
|
|
|
M->lib.dispatch_walltime = dispatch_walltime;
|
2023-09-12 04:34:53 +00:00
|
|
|
M->lib.pthread_self = pthread_self;
|
|
|
|
M->lib.dispatch_release = dispatch_release;
|
2023-11-12 06:32:12 +00:00
|
|
|
M->lib.raise = sys_raise;
|
Make improvements
- Every unit test now passes on Apple Silicon. The final piece of this
puzzle was porting our POSIX threads cancelation support, since that
works differently on ARM64 XNU vs. AMD64. Our semaphore support on
Apple Silicon is also superior now compared to AMD64, thanks to the
grand central dispatch library which lets *NSYNC locks go faster.
- The Cosmopolitan runtime is now more stable, particularly on Windows.
To do this, thread local storage is mandatory at all runtime levels,
and the innermost packages of the C library is no longer being built
using ASAN. TLS is being bootstrapped with a 128-byte TIB during the
process startup phase, and then later on the runtime re-allocates it
either statically or dynamically to support code using _Thread_local.
fork() and execve() now do a better job cooperating with threads. We
can now check how much stack memory is left in the process or thread
when functions like kprintf() / execve() etc. call alloca(), so that
ENOMEM can be raised, reduce a buffer size, or just print a warning.
- POSIX signal emulation is now implemented the same way kernels do it
with pthread_kill() and raise(). Any thread can interrupt any other
thread, regardless of what it's doing. If it's blocked on read/write
then the killer thread will cancel its i/o operation so that EINTR can
be returned in the mark thread immediately. If it's doing a tight CPU
bound operation, then that's also interrupted by the signal delivery.
Signal delivery works now by suspending a thread and pushing context
data structures onto its stack, and redirecting its execution to a
trampoline function, which calls SetThreadContext(GetCurrentThread())
when it's done.
- We're now doing a better job managing locks and handles. On NetBSD we
now close semaphore file descriptors in forked children. Semaphores on
Windows can now be canceled immediately, which means mutexes/condition
variables will now go faster. Apple Silicon semaphores can be canceled
too. We're now using Apple's pthread_yield() funciton. Apple _nocancel
syscalls are now used on XNU when appropriate to ensure pthread_cancel
requests aren't lost. The MbedTLS library has been updated to support
POSIX thread cancelations. See tool/build/runitd.c for an example of
how it can be used for production multi-threaded tls servers. Handles
on Windows now leak less often across processes. All i/o operations on
Windows are now overlapped, which means file pointers can no longer be
inherited across dup() and fork() for the time being.
- We now spawn a thread on Windows to deliver SIGCHLD and wakeup wait4()
which means, for example, that posix_spawn() now goes 3x faster. POSIX
spawn is also now more correct. Like Musl, it's now able to report the
failure code of execve() via a pipe although our approach favors using
shared memory to do that on systems that have a true vfork() function.
- We now spawn a thread to deliver SIGALRM to threads when setitimer()
is used. This enables the most precise wakeups the OS makes possible.
- The Cosmopolitan runtime now uses less memory. On NetBSD for example,
it turned out the kernel would actually commit the PT_GNU_STACK size
which caused RSS to be 6mb for every process. Now it's down to ~4kb.
On Apple Silicon, we reduce the mandatory upstream thread size to the
smallest possible size to reduce the memory overhead of Cosmo threads.
The examples directory has a program called greenbean which can spawn
a web server on Linux with 10,000 worker threads and have the memory
usage of the process be ~77mb. The 1024 byte overhead of POSIX-style
thread-local storage is now optional; it won't be allocated until the
pthread_setspecific/getspecific functions are called. On Windows, the
threads that get spawned which are internal to the libc implementation
use reserve rather than commit memory, which shaves a few hundred kb.
- sigaltstack() is now supported on Windows, however it's currently not
able to be used to handle stack overflows, since crash signals are
still generated by WIN32. However the crash handler will still switch
to the alt stack, which is helpful in environments with tiny threads.
- Test binaries are now smaller. Many of the mandatory dependencies of
the test runner have been removed. This ensures many programs can do a
better job only linking the the thing they're testing. This caused the
test binaries for LIBC_FMT for example, to decrease from 200kb to 50kb
- long double is no longer used in the implementation details of libc,
except in the APIs that define it. The old code that used long double
for time (instead of struct timespec) has now been thoroughly removed.
- ShowCrashReports() is now much tinier in MODE=tiny. Instead of doing
backtraces itself, it'll just print a command you can run on the shell
using our new `cosmoaddr2line` program to view the backtrace.
- Crash report signal handling now works in a much better way. Instead
of terminating the process, it now relies on SA_RESETHAND so that the
default SIG_IGN behavior can terminate the process if necessary.
- Our pledge() functionality has now been fully ported to AARCH64 Linux.
2023-09-19 03:44:45 +00:00
|
|
|
M->lib.pthread_join = pthread_join;
|
|
|
|
M->lib.pthread_yield_np = pthread_yield_np;
|
|
|
|
M->lib.pthread_stack_min = PTHREAD_STACK_MIN;
|
|
|
|
M->lib.sizeof_pthread_attr_t = sizeof(pthread_attr_t);
|
|
|
|
M->lib.pthread_attr_init = pthread_attr_init;
|
|
|
|
M->lib.pthread_attr_destroy = pthread_attr_destroy;
|
|
|
|
M->lib.pthread_attr_setstacksize = pthread_attr_setstacksize;
|
|
|
|
M->lib.pthread_attr_setguardsize = pthread_attr_setguardsize;
|
2023-09-21 14:30:39 +00:00
|
|
|
M->lib.exit = exit;
|
|
|
|
M->lib.close = sys_close;
|
|
|
|
M->lib.munmap = sys_munmap;
|
|
|
|
M->lib.openat = sys_openat;
|
|
|
|
M->lib.write = sys_write;
|
|
|
|
M->lib.read = sys_read;
|
|
|
|
M->lib.sigaction = sys_sigaction;
|
|
|
|
M->lib.pselect = sys_pselect;
|
|
|
|
M->lib.mprotect = sys_mprotect;
|
2023-10-10 03:18:48 +00:00
|
|
|
M->lib.sigaltstack = sys_sigaltstack;
|
|
|
|
M->lib.getentropy = sys_getentropy;
|
|
|
|
M->lib.sem_open = sys_sem_open;
|
|
|
|
M->lib.sem_unlink = sys_sem_unlink;
|
|
|
|
M->lib.sem_close = sys_sem_close;
|
|
|
|
M->lib.sem_post = sys_sem_post;
|
|
|
|
M->lib.sem_wait = sys_sem_wait;
|
|
|
|
M->lib.sem_trywait = sys_sem_trywait;
|
2023-10-10 06:12:32 +00:00
|
|
|
M->lib.getrlimit = sys_getrlimit;
|
|
|
|
M->lib.setrlimit = sys_setrlimit;
|
2023-11-03 13:04:13 +00:00
|
|
|
M->lib.dlopen = dlopen;
|
|
|
|
M->lib.dlsym = dlsym;
|
|
|
|
M->lib.dlclose = dlclose;
|
|
|
|
M->lib.dlerror = dlerror;
|
2024-02-01 11:39:46 +00:00
|
|
|
M->lib.pthread_cpu_number_np = pthread_cpu_number_np;
|
2024-05-03 06:21:43 +00:00
|
|
|
M->lib.sysctl = sys_sysctl;
|
|
|
|
M->lib.sysctlbyname = sys_sysctlbyname;
|
|
|
|
M->lib.sysctlnametomib = sys_sysctlnametomib;
|
2023-05-19 02:05:08 +00:00
|
|
|
|
2023-08-17 14:21:13 +00:00
|
|
|
/* getenv("_") is close enough to at_execfn */
|
2024-01-07 15:13:20 +00:00
|
|
|
execfn = 0;
|
2023-05-19 02:05:08 +00:00
|
|
|
for (i = 0; envp[i]; ++i) {
|
|
|
|
if (envp[i][0] == '_' && envp[i][1] == '=') {
|
|
|
|
execfn = envp[i] + 2;
|
|
|
|
}
|
|
|
|
}
|
2024-01-07 15:13:20 +00:00
|
|
|
prog = GetEnv(envp + i + 1, "executable_path");
|
|
|
|
if (!execfn) {
|
|
|
|
execfn = prog;
|
|
|
|
}
|
2023-05-19 02:05:08 +00:00
|
|
|
|
2023-08-17 14:21:13 +00:00
|
|
|
/* sneak the system five abi back out of args */
|
|
|
|
sp = (long *)(argv - 1);
|
|
|
|
auxv = (long *)(envp + i + 1);
|
|
|
|
|
2023-11-12 13:48:18 +00:00
|
|
|
/* create new bottom of stack for spawned program
|
|
|
|
system v abi aligns this on a 16-byte boundary
|
|
|
|
grows down the alloc by poking the guard pages */
|
Loader path security (#1012)
The ape loader now passes the program executable name directly as a
register. `x2` is used on aarch64, `%rdx` on x86_64. This is passed
as the third argument to `cosmo()` (M1) or `Launch` (non-M1) and is
assigned to the global `__program_executable_name`.
`GetProgramExecutableName` now returns this global's value, setting
it if it is initially null. `InitProgramExecutableName` first tries
exotic, secure methods: `KERN_PROC_PATHNAME` on FreeBSD/NetBSD, and
`/proc` on Linux. If those produce a reasonable response (i.e., not
`"/usr/bin/ape"`, which happens with the loader before this change),
that is used. Otherwise, if `issetugid()`, the empty string is used.
Otherwise, the old argv/envp parsing code is run.
The value returned from the loader is always the full absolute path
of the binary to be executed, having passed through `realpath`. For
the non-M1 loader, this necessitated writing `RealPath`, which uses
`readlinkat` of `"/proc/self/fd/[progfd]"` on Linux, `F_GETPATH` on
Xnu, and the `__realpath` syscall on OpenBSD. On FreeBSD/NetBSD, it
punts to `GetProgramExecutableName`, which is secure on those OSes.
With the loader, all platforms now have a secure program executable
name. With no loader or an old loader, everything still works as it
did, but setuid/setgid is not supported if the insecure pathfinding
code would have been needed.
Fixes #991.
2023-12-15 17:23:58 +00:00
|
|
|
n = (auxv - sp + AUXV_WORDS + 1) * sizeof(long);
|
2023-11-12 13:48:18 +00:00
|
|
|
sp2 = (long *)__builtin_alloca(n);
|
|
|
|
if ((long)sp2 & 15)
|
|
|
|
++sp2;
|
|
|
|
for (; n > 0; n -= pagesz) {
|
|
|
|
((char *)sp2)[n - 1] = 0;
|
|
|
|
}
|
2023-12-04 20:45:46 +00:00
|
|
|
memmove(sp2, sp, (auxv - sp) * sizeof(long));
|
2023-11-12 13:48:18 +00:00
|
|
|
argv = (char **)(sp2 + 1);
|
|
|
|
envp = (char **)(sp2 + 1 + argc + 1);
|
$prog.ape support (#977)
* ape loader: $prog.ape + login shell support
If the ape loader is invoked with `$0 = $prog.ape`, then it searches for
a `$prog` in the same directory as it and loads that. In particular, the
loader searches the `PATH` for an executable named `$prog.ape`, then for
an executable named `$prog` in the same directory. If the former but not
the latter is found, the search terminates with an error.
It also handles the special case of getting started as `-$SHELL`, which
getty uses to indicate that the shell is a login shell. The path is not
searched in this case, and the program location is read straight out of
the `SHELL` variable.
It is now possible to have `/usr/local/bin/zsh.ape` act as a login shell
for a `/usr/local/bin/zsh` αpε, insofar as the program will get started
with the 'correct' args. Unfortunately, many things break if `$0` is not
the actual full path of the executable being run; for example, backspace
does not update the display properly.
To work around the brokenness introduced by not having `$0` be the full
path of the binary, we cut the leading `-` out of `argv[0]` if present.
This gets the loader's behavior with `$prog.ape` up to par, but doesn't
tell login shells that they are login shells.
So we introduce a hack to accomplish that: if ape is run as `-$prog.ape`
and the shell is `$prog`, the binary that is loaded has a `-l` flag put
into its first argument.
As of this commit, αpε binaries can be used as login shells on OSX.
* if islogin, execfn = shell
Prior to this, execfn was not being properly set for login shells that
did not receive `$_`, which was the case for iTerm2 on Mac. There were
no observable consequences of this, but fixing it seems good anyway.
* Fix auxv location calculation
In the non-login-shell case, it was leaving a word of uninitialized
memory at `envp[i] + 1`. This reuses the previous calculation based
on `envp`.
2023-12-04 03:39:32 +00:00
|
|
|
auxv = (long *)(envp + i + 1);
|
2023-11-12 13:48:18 +00:00
|
|
|
sp = sp2;
|
|
|
|
|
2023-08-17 14:21:13 +00:00
|
|
|
/* interpret command line arguments */
|
2024-01-07 15:13:20 +00:00
|
|
|
if ((M->ps.indirect = GetIndirectOffset(prog))) {
|
|
|
|
/* if called as $prog.ape, then strip off the .ape and run the
|
$prog.ape support (#977)
* ape loader: $prog.ape + login shell support
If the ape loader is invoked with `$0 = $prog.ape`, then it searches for
a `$prog` in the same directory as it and loads that. In particular, the
loader searches the `PATH` for an executable named `$prog.ape`, then for
an executable named `$prog` in the same directory. If the former but not
the latter is found, the search terminates with an error.
It also handles the special case of getting started as `-$SHELL`, which
getty uses to indicate that the shell is a login shell. The path is not
searched in this case, and the program location is read straight out of
the `SHELL` variable.
It is now possible to have `/usr/local/bin/zsh.ape` act as a login shell
for a `/usr/local/bin/zsh` αpε, insofar as the program will get started
with the 'correct' args. Unfortunately, many things break if `$0` is not
the actual full path of the executable being run; for example, backspace
does not update the display properly.
To work around the brokenness introduced by not having `$0` be the full
path of the binary, we cut the leading `-` out of `argv[0]` if present.
This gets the loader's behavior with `$prog.ape` up to par, but doesn't
tell login shells that they are login shells.
So we introduce a hack to accomplish that: if ape is run as `-$prog.ape`
and the shell is `$prog`, the binary that is loaded has a `-l` flag put
into its first argument.
As of this commit, αpε binaries can be used as login shells on OSX.
* if islogin, execfn = shell
Prior to this, execfn was not being properly set for login shells that
did not receive `$_`, which was the case for iTerm2 on Mac. There were
no observable consequences of this, but fixing it seems good anyway.
* Fix auxv location calculation
In the non-login-shell case, it was leaving a word of uninitialized
memory at `envp[i] + 1`. This reuses the previous calculation based
on `envp`.
2023-12-04 03:39:32 +00:00
|
|
|
$prog. This allows you to use symlinks to trick the OS when
|
|
|
|
a native executable is required. For example, let's say you
|
|
|
|
want to use the APE binary /opt/cosmos/bin/bash as a system
|
|
|
|
shell in /etc/shells, or perhaps in shebang lines like e.g.
|
|
|
|
`#!/opt/cosmos/bin/bash`. That won't work with APE normally,
|
|
|
|
but it will if you say:
|
|
|
|
ln -sf /usr/local/bin/ape /opt/cosmos/bin/bash.ape
|
|
|
|
and then use #!/opt/cosmos/bin/bash.ape instead. */
|
2024-01-07 15:13:20 +00:00
|
|
|
M->ps.literally = 1;
|
$prog.ape support (#977)
* ape loader: $prog.ape + login shell support
If the ape loader is invoked with `$0 = $prog.ape`, then it searches for
a `$prog` in the same directory as it and loads that. In particular, the
loader searches the `PATH` for an executable named `$prog.ape`, then for
an executable named `$prog` in the same directory. If the former but not
the latter is found, the search terminates with an error.
It also handles the special case of getting started as `-$SHELL`, which
getty uses to indicate that the shell is a login shell. The path is not
searched in this case, and the program location is read straight out of
the `SHELL` variable.
It is now possible to have `/usr/local/bin/zsh.ape` act as a login shell
for a `/usr/local/bin/zsh` αpε, insofar as the program will get started
with the 'correct' args. Unfortunately, many things break if `$0` is not
the actual full path of the executable being run; for example, backspace
does not update the display properly.
To work around the brokenness introduced by not having `$0` be the full
path of the binary, we cut the leading `-` out of `argv[0]` if present.
This gets the loader's behavior with `$prog.ape` up to par, but doesn't
tell login shells that they are login shells.
So we introduce a hack to accomplish that: if ape is run as `-$prog.ape`
and the shell is `$prog`, the binary that is loaded has a `-l` flag put
into its first argument.
As of this commit, αpε binaries can be used as login shells on OSX.
* if islogin, execfn = shell
Prior to this, execfn was not being properly set for login shells that
did not receive `$_`, which was the case for iTerm2 on Mac. There were
no observable consequences of this, but fixing it seems good anyway.
* Fix auxv location calculation
In the non-login-shell case, it was leaving a word of uninitialized
memory at `envp[i] + 1`. This reuses the previous calculation based
on `envp`.
2023-12-04 03:39:32 +00:00
|
|
|
argc = sp[0];
|
|
|
|
argv = (char **)(sp + 1);
|
|
|
|
} else if ((M->ps.literally = argc >= 3 && !StrCmp(argv[1], "-"))) {
|
2023-08-17 14:21:13 +00:00
|
|
|
/* if the first argument is a hyphen then we give the user the
|
|
|
|
power to change argv[0] or omit it entirely. most operating
|
|
|
|
systems don't permit the omission of argv[0] but we do, b/c
|
|
|
|
it's specified by ANSI X3.159-1988. */
|
2023-05-19 02:05:08 +00:00
|
|
|
prog = (char *)sp[3];
|
|
|
|
argc = sp[3] = sp[0] - 3;
|
|
|
|
argv = (char **)((sp += 3) + 1);
|
|
|
|
} else if (argc < 2) {
|
2023-07-25 12:43:04 +00:00
|
|
|
Emit("usage: ape PROG [ARGV1,ARGV2,...]\n"
|
|
|
|
" ape - PROG [ARGV0,ARGV1,...]\n"
|
2024-01-07 15:35:50 +00:00
|
|
|
" PROG.ape [ARGV1,ARGV2,...]\n"
|
2023-12-31 19:43:13 +00:00
|
|
|
"actually portable executable loader silicon 1.10\n"
|
|
|
|
"copyrights 2023 justine alexandra roberts tunney\n"
|
2023-05-19 02:05:08 +00:00
|
|
|
"https://justine.lol/ape.html\n");
|
|
|
|
_exit(1);
|
|
|
|
} else {
|
|
|
|
prog = (char *)sp[2];
|
|
|
|
argc = sp[1] = sp[0] - 1;
|
|
|
|
argv = (char **)((sp += 1) + 1);
|
|
|
|
}
|
|
|
|
|
2023-08-17 14:21:13 +00:00
|
|
|
/* allocate ephemeral memory for reading file */
|
|
|
|
n = sizeof(union ElfEhdrBuf);
|
2023-08-18 05:01:42 +00:00
|
|
|
ebuf = (union ElfEhdrBuf *)__builtin_alloca(n);
|
2023-08-17 14:21:13 +00:00
|
|
|
for (; n > 0; n -= pagesz) {
|
|
|
|
((char *)ebuf)[n - 1] = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* search for executable */
|
2023-06-16 23:58:30 +00:00
|
|
|
if (!(exe = Commandv(&M->ps, prog, GetEnv(envp, "PATH")))) {
|
|
|
|
Pexit(prog, 0, "not found (maybe chmod +x)");
|
2023-11-05 09:36:47 +00:00
|
|
|
} else if ((fd = sys_openat(AT_FDCWD, exe, O_RDONLY, 0)) < 0) {
|
|
|
|
Pexit(exe, fd, "open");
|
|
|
|
} else if ((rc = sys_read(fd, ebuf->buf, sizeof(ebuf->buf))) < 0) {
|
|
|
|
Pexit(exe, rc, "read");
|
2023-08-17 14:21:13 +00:00
|
|
|
} else if ((unsigned long)rc < sizeof(ebuf->ehdr)) {
|
2023-06-16 23:58:30 +00:00
|
|
|
Pexit(exe, 0, "too small");
|
|
|
|
}
|
2023-08-17 14:21:13 +00:00
|
|
|
pe = ebuf->buf + rc;
|
2023-06-16 23:58:30 +00:00
|
|
|
|
2023-08-17 14:21:13 +00:00
|
|
|
/* generate some hard random data */
|
2023-11-05 09:36:47 +00:00
|
|
|
if ((rc = sys_getentropy(M->rando, sizeof(M->rando))) < 0) {
|
|
|
|
Pexit(argv[0], rc, "getentropy");
|
2023-08-17 14:21:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* ape intended behavior
|
|
|
|
1. if ape, will scan shell script for elf printf statements
|
|
|
|
2. shell script may have multiple lines producing elf headers
|
|
|
|
3. all elf printf lines must exist in the first 8192 bytes of file
|
|
|
|
4. elf program headers may appear anywhere in the binary */
|
|
|
|
if (READ64(ebuf->buf) == READ64("MZqFpD='") ||
|
|
|
|
READ64(ebuf->buf) == READ64("jartsr='") ||
|
|
|
|
READ64(ebuf->buf) == READ64("APEDBG='")) {
|
|
|
|
for (p = ebuf->buf; p < pe; ++p) {
|
2023-07-25 12:43:04 +00:00
|
|
|
if (READ64(p) != READ64("printf '")) {
|
2023-05-19 02:05:08 +00:00
|
|
|
continue;
|
|
|
|
}
|
2023-07-25 12:43:04 +00:00
|
|
|
for (i = 0, p += 8; p + 3 < pe && (c = *p++) != '\'';) {
|
2023-05-19 02:05:08 +00:00
|
|
|
if (c == '\\') {
|
|
|
|
if ('0' <= *p && *p <= '7') {
|
|
|
|
c = *p++ - '0';
|
|
|
|
if ('0' <= *p && *p <= '7') {
|
|
|
|
c *= 8;
|
|
|
|
c += *p++ - '0';
|
|
|
|
if ('0' <= *p && *p <= '7') {
|
|
|
|
c *= 8;
|
|
|
|
c += *p++ - '0';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-08-17 14:21:13 +00:00
|
|
|
ebuf->buf[i++] = c;
|
|
|
|
if (i >= sizeof(ebuf->buf)) {
|
2023-07-25 12:43:04 +00:00
|
|
|
break;
|
|
|
|
}
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
2023-08-17 14:21:13 +00:00
|
|
|
if (i >= sizeof(ebuf->ehdr)) {
|
|
|
|
TryElf(M, ebuf, exe, fd, sp, auxv, execfn);
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-08-17 14:21:13 +00:00
|
|
|
Pexit(exe, 0, TryElf(M, ebuf, exe, fd, sp, auxv, execfn));
|
2023-05-19 02:05:08 +00:00
|
|
|
}
|