Mint APE Loader v1.3

This version has better error messages and safety checks. It supports
loading static position-independent executables. It correctly handles
more kinds of weird ELF program header layouts. A force flag has been
added to avoid system execve(). Finally the longstanding misalignment
with our ELF PT_NOTE section has been addressed.
This commit is contained in:
Justine Tunney 2023-07-23 17:07:38 -07:00
parent 82b1e61443
commit 3d172c99fe
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
19 changed files with 1001 additions and 470 deletions

View file

@ -77,9 +77,6 @@
* @note this can probably be used as a binfmt_misc interpreter
*/
#define PAGE_SIZE 4096
#define NULL_PAGE 2097152
#define LINUX 1
#define XNU 8
#define OPENBSD 16
@ -102,6 +99,7 @@
#define IsNetbsd() (SupportsNetbsd() && os == NETBSD)
#define O_RDONLY 0
#define PROT_NONE 0
#define PROT_READ 1
#define PROT_WRITE 2
#define PROT_EXEC 4
@ -109,10 +107,12 @@
#define MAP_PRIVATE 2
#define MAP_FIXED 16
#define MAP_ANONYMOUS (IsLinux() ? 32 : 4096)
#define MAP_NORESERVE (IsLinux() ? 16384 : 0)
#define ELFCLASS32 1
#define ELFDATA2LSB 1
#define EM_NEXGEN32E 62
#define ET_EXEC 2
#define ET_DYN 3
#define PT_LOAD 1
#define PT_DYNAMIC 2
#define PT_INTERP 3
@ -124,6 +124,7 @@
#define AT_PHDR 3
#define AT_PHENT 4
#define AT_PHNUM 5
#define AT_PAGESZ 6
#define AT_EXECFN_LINUX 31
#define AT_EXECFN_NETBSD 2014
#define X_OK 1
@ -132,11 +133,11 @@
#define PR_SET_MM 35
#define PR_SET_MM_EXE_FILE 13
#define Read32(S) \
#define READ32(S) \
((unsigned)(255 & (S)[3]) << 030 | (unsigned)(255 & (S)[2]) << 020 | \
(unsigned)(255 & (S)[1]) << 010 | (unsigned)(255 & (S)[0]) << 000)
#define Read64(S) \
#define READ64(S) \
((unsigned long)(255 & (S)[7]) << 070 | \
(unsigned long)(255 & (S)[6]) << 060 | \
(unsigned long)(255 & (S)[5]) << 050 | \
@ -146,6 +147,13 @@
(unsigned long)(255 & (S)[1]) << 010 | \
(unsigned long)(255 & (S)[0]) << 000)
#define DEBUG(VAR) \
{ \
char ibuf[19] = {0}; \
Utox(ibuf, VAR); \
Print(os, 2, #VAR " ", ibuf, "\n", 0l); \
}
struct ElfEhdr {
unsigned char e_ident[16];
unsigned short e_type;
@ -176,7 +184,7 @@ struct ElfPhdr {
union ElfEhdrBuf {
struct ElfEhdr ehdr;
char buf[4096];
char buf[8192];
};
union ElfPhdrBuf {
@ -199,8 +207,8 @@ struct ApeLoader {
char path[1024];
};
long SystemCall(long arg1, long arg2, long arg3, long arg4, long arg5,
long arg6, long arg7, long magi);
long SystemCall(long, long, long, long, long, long, long, int);
void Launch(void *, long, void *, int) __attribute__((__noreturn__));
extern char __executable_start[];
extern char _end[];
@ -215,6 +223,27 @@ static unsigned long StrLen(const char *s) {
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);
}
static void Bzero(void *a, unsigned long n) {
long z;
char *p, *e;
p = (char *)a;
e = p + n;
z = 0;
while (p + sizeof(z) <= e) {
__builtin_memcpy(p, &z, sizeof(z));
p += sizeof(z);
}
while (p < e) {
*p++ = 0;
}
}
static const char *MemChr(const char *s, unsigned char c, unsigned long n) {
for (; n; --n, ++s) {
if ((*s & 255) == c) {
@ -225,15 +254,29 @@ static const char *MemChr(const char *s, unsigned char c, unsigned long n) {
}
static void *MemMove(void *a, const void *b, unsigned long n) {
char *d = a;
long w;
char *d;
const char *s;
unsigned long i;
const char *s = b;
d = (char *)a;
s = (const char *)b;
if (d > s) {
for (i = n; i--;) {
d[i] = s[i];
while (n >= sizeof(w)) {
n -= sizeof(w);
__builtin_memcpy(&w, s + n, sizeof(n));
__builtin_memcpy(d + n, &w, sizeof(n));
}
while (n--) {
d[n] = s[n];
}
} else {
for (i = 0; i < n; ++i) {
i = 0;
while (i + sizeof(w) <= n) {
__builtin_memcpy(&w, s + i, sizeof(i));
__builtin_memcpy(d + i, &w, sizeof(i));
i += sizeof(w);
}
for (; i < n; ++i) {
d[i] = s[i];
}
}
@ -260,6 +303,23 @@ static char *GetEnv(char **p, const char *s) {
return 0;
}
static char *Utox(char p[19], unsigned long x) {
int i;
if (x) {
*p++ = '0';
*p++ = 'x';
i = (__builtin_clzl(x) ^ (sizeof(long) * 8 - 1)) + 1;
i = (i + 3) & -4;
do {
*p++ = "0123456789abcdef"[(x >> (i -= 4)) & 15];
} while (i);
} else {
*p++ = '0';
}
*p = 0;
return p;
}
static char *Utoa(char p[21], unsigned long x) {
char t;
unsigned long i, a, b;
@ -284,15 +344,22 @@ static char *Itoa(char p[21], long x) {
return Utoa(p, x);
}
__attribute__((__noreturn__)) static void Exit(int rc, int os) {
SystemCall(rc, 0, 0, 0, 0, 0, 0,
(IsLinux() ? 60 : 1) | (IsXnu() ? 0x2000000 : 0));
__attribute__((__noinline__)) static long CallSystem(long arg1, long arg2,
long arg3, long arg4,
long arg5, long arg6,
long arg7, int numba,
char os) {
if (IsXnu()) numba |= 0x2000000;
return SystemCall(arg1, arg2, arg3, arg4, arg5, arg6, arg7, numba);
}
__attribute__((__noreturn__)) static void Exit(long rc, int os) {
CallSystem(rc, 0, 0, 0, 0, 0, 0, IsLinux() ? 60 : 1, os);
__builtin_unreachable();
}
static int Close(int fd, int os) {
return SystemCall(fd, 0, 0, 0, 0, 0, 0,
(IsLinux() ? 3 : 6) | (IsXnu() ? 0x2000000 : 0));
return CallSystem(fd, 0, 0, 0, 0, 0, 0, IsLinux() ? 3 : 6, os);
}
static long Pread(int fd, void *data, unsigned long size, long off, int os) {
@ -314,18 +381,15 @@ static long Pread(int fd, void *data, unsigned long size, long off, int os) {
}
static long Write(int fd, const void *data, unsigned long size, int os) {
return SystemCall(fd, (long)data, size, 0, 0, 0, 0,
(IsLinux() ? 1 : 4) | (IsXnu() ? 0x2000000 : 0));
return CallSystem(fd, (long)data, size, 0, 0, 0, 0, IsLinux() ? 1 : 4, os);
}
static int Execve(const char *prog, char **argv, char **envp, int os) {
return SystemCall((long)prog, (long)argv, (long)envp, 0, 0, 0, 0,
59 | (IsXnu() ? 0x2000000 : 0));
return CallSystem((long)prog, (long)argv, (long)envp, 0, 0, 0, 0, 59, os);
}
static int Access(const char *path, int mode, int os) {
return SystemCall((long)path, mode, 0, 0, 0, 0, 0,
(IsLinux() ? 21 : 33) | (IsXnu() ? 0x2000000 : 0));
return CallSystem((long)path, mode, 0, 0, 0, 0, 0, IsLinux() ? 21 : 33, os);
}
static int Msyscall(long p, unsigned long n, int os) {
@ -337,13 +401,12 @@ static int Msyscall(long p, unsigned long n, int os) {
}
static int Open(const char *path, int flags, int mode, int os) {
return SystemCall((long)path, flags, mode, 0, 0, 0, 0,
(IsLinux() ? 2 : 5) | (IsXnu() ? 0x2000000 : 0));
return CallSystem((long)path, flags, mode, 0, 0, 0, 0, IsLinux() ? 2 : 5, os);
}
static int Mprotect(void *addr, unsigned long size, int prot, int os) {
return SystemCall((long)addr, size, prot, 0, 0, 0, 0,
(IsLinux() ? 10 : 74) | (IsXnu() ? 0x2000000 : 0));
return CallSystem((long)addr, size, prot, 0, 0, 0, 0, IsLinux() ? 10 : 74,
os);
}
static long Mmap(void *addr, unsigned long size, int prot, int flags, int fd,
@ -396,21 +459,6 @@ __attribute__((__noreturn__)) static void Pexit(int os, const char *c, int rc,
Exit(127, os);
}
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);
}
static void *MemSet(void *a, int c, unsigned long n) {
char *d = a;
unsigned long i;
for (i = 0; i < n; ++i) {
d[i] = c;
}
return d;
}
static char EndsWithIgnoreCase(const char *p, unsigned long n, const char *s) {
unsigned long i, m;
if (n >= (m = StrLen(s))) {
@ -466,8 +514,6 @@ static char FindCommand(struct PathSearcher *ps, const char *suffix) {
MemChr(ps->name, '\\', ps->namelen)) {
ps->path[0] = 0;
return AccessCommand(ps, suffix, 0);
} else {
if (AccessCommand(ps, suffix, 0)) return 1;
}
return SearchPath(ps, suffix);
}
@ -486,7 +532,8 @@ static char *Commandv(struct PathSearcher *ps, int os, const char *name,
}
__attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd,
long *sp, struct ElfEhdr *e,
long *sp, unsigned long pagesz,
struct ElfEhdr *e,
struct ElfPhdr *p) {
long rc;
int prot;
@ -494,21 +541,26 @@ __attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd,
int found_code;
int found_entry;
long code, codesize;
unsigned long dynbase;
unsigned long virtmin, virtmax;
unsigned long a, b, c, d, i, j;
/* load elf */
/* validate elf */
code = 0;
codesize = 0;
found_code = 0;
found_entry = 0;
virtmin = virtmax = 0;
if (!pagesz) pagesz = 4096;
if (pagesz & (pagesz - 1)) {
Pexit(os, exe, 0, "AT_PAGESZ isn't two power");
}
for (i = 0; i < e->e_phnum; ++i) {
/* validate program header */
if (p[i].p_type == PT_INTERP) {
Pexit(os, exe, 0, "ELF has PT_INTERP which is unsupported");
Pexit(os, exe, 0, "ELF has PT_INTERP which isn't supported");
}
if (p[i].p_type == PT_DYNAMIC) {
Pexit(os, exe, 0, "ELF has PT_DYNAMIC which is unsupported");
Pexit(os, exe, 0, "ELF has PT_DYNAMIC which isn't supported");
}
if (p[i].p_type != PT_LOAD) {
continue;
@ -517,48 +569,34 @@ __attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd,
continue;
}
if (p[i].p_filesz > p[i].p_memsz) {
Pexit(os, exe, 0, "ELF phdr filesz exceeds memsz");
Pexit(os, exe, 0, "ELF p_filesz exceeds p_memsz");
}
if ((p[i].p_vaddr & (PAGE_SIZE - 1)) != (p[i].p_offset & (PAGE_SIZE - 1))) {
Pexit(os, exe, 0, "ELF phdr virt/off skew mismatch w.r.t. pagesize");
if ((p[i].p_vaddr & (pagesz - 1)) != (p[i].p_offset & (pagesz - 1))) {
Pexit(os, exe, 0, "ELF p_vaddr incongruent w/ p_offset modulo AT_PAGESZ");
}
if (p[i].p_vaddr + p[i].p_memsz < p[i].p_vaddr ||
p[i].p_vaddr + p[i].p_memsz + (PAGE_SIZE - 1) < p[i].p_vaddr) {
Pexit(os, exe, 0, "ELF phdr vaddr+memsz overflow");
p[i].p_vaddr + p[i].p_memsz + (pagesz - 1) < p[i].p_vaddr) {
Pexit(os, exe, 0, "ELF p_vaddr + p_memsz overflow");
}
if (p[i].p_vaddr + p[i].p_filesz < p[i].p_vaddr ||
p[i].p_vaddr + p[i].p_filesz + (PAGE_SIZE - 1) < p[i].p_vaddr) {
Pexit(os, exe, 0, "ELF phdr vaddr+files overflow");
}
a = p[i].p_vaddr & -PAGE_SIZE;
b = (p[i].p_vaddr + p[i].p_memsz + (PAGE_SIZE - 1)) & -PAGE_SIZE;
if (MAX(a, 0) < MIN(b, NULL_PAGE)) {
Pexit(os, exe, 0, "ELF overlaps NULL page");
p[i].p_vaddr + p[i].p_filesz + (pagesz - 1) < p[i].p_vaddr) {
Pexit(os, exe, 0, "ELF p_vaddr + p_filesz overflow");
}
a = p[i].p_vaddr & -pagesz;
b = (p[i].p_vaddr + p[i].p_memsz + (pagesz - 1)) & -pagesz;
if (MAX(a, (unsigned long)__executable_start) <
MIN(b, (unsigned long)_end)) {
Pexit(os, exe, 0, "ELF overlaps your APE loader");
Pexit(os, exe, 0, "ELF segments overlap your APE loader");
}
for (j = i + 1; j < e->e_phnum; ++j) {
if (p[j].p_type != PT_LOAD) continue;
c = p[j].p_vaddr & -PAGE_SIZE;
d = (p[j].p_vaddr + p[j].p_memsz + (PAGE_SIZE - 1)) & -PAGE_SIZE;
c = p[j].p_vaddr & -pagesz;
d = (p[j].p_vaddr + p[j].p_memsz + (pagesz - 1)) & -pagesz;
if (MAX(a, c) < MIN(b, d)) {
Pexit(os, exe, 0, "ELF overlaps its own vaspace");
Pexit(os, exe, 0, "ELF segments overlap each others virtual memory");
}
}
/* 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;
if (!found_code) {
code = p[i].p_vaddr;
codesize = p[i].p_filesz;
@ -568,30 +606,65 @@ __attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd,
found_entry = 1;
}
}
if (p[i].p_vaddr < virtmin) {
virtmin = p[i].p_vaddr;
}
if (p[i].p_vaddr + p[i].p_memsz > virtmax) {
virtmax = p[i].p_vaddr + p[i].p_memsz;
}
}
if (!found_entry) {
Pexit(os, 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) {
rc = Mmap(0, virtmax - virtmin, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0, os);
if (rc < 0) Pexit(os, exe, rc, "pie mmap");
dynbase = rc;
if (dynbase & (pagesz - 1)) {
Pexit(os, exe, 0, "OS mmap incongruent w/ AT_PAGESZ");
}
if (dynbase + virtmin < dynbase) {
Pexit(os, exe, 0, "ELF dynamic base overflow");
}
} else {
dynbase = 0;
}
/* load elf */
for (i = 0; i < e->e_phnum; ++i) {
if (p[i].p_type != PT_LOAD) continue;
if (!p[i].p_memsz) 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 */
if (p[i].p_filesz) {
void *addr;
int prot1, prot2;
unsigned long size;
int dirty, prot1, prot2;
dirty = 0;
prot1 = prot;
prot2 = prot;
a = p[i].p_vaddr + p[i].p_filesz;
b = (a + (PAGE_SIZE - 1)) & -PAGE_SIZE;
c = p[i].p_vaddr + p[i].p_memsz;
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 */
if (b > c) b = c;
if (c > b) {
dirty = 1;
if (~prot1 & PROT_WRITE) {
prot1 = PROT_READ | PROT_WRITE;
}
if (c > b && (~prot1 & PROT_WRITE)) {
prot1 = PROT_READ | PROT_WRITE;
}
addr = (void *)(p[i].p_vaddr & -PAGE_SIZE);
size = (p[i].p_vaddr & (PAGE_SIZE - 1)) + p[i].p_filesz;
rc = Mmap(addr, size, prot1, flags, fd, p[i].p_offset & -PAGE_SIZE, os);
addr = (void *)(dynbase + (p[i].p_vaddr & -pagesz));
size = (p[i].p_vaddr & (pagesz - 1)) + p[i].p_filesz;
rc = Mmap(addr, size, prot1, flags, fd, p[i].p_offset & -pagesz, os);
if (rc < 0) Pexit(os, exe, rc, "prog mmap");
if (dirty) MemSet((void *)a, 0, b - a);
if (c > b) Bzero((void *)(dynbase + a), b - a);
if (prot2 != prot1) {
rc = Mprotect(addr, size, prot2, os);
if (rc < 0) Pexit(os, exe, rc, "prog mprotect");
@ -600,83 +673,100 @@ __attribute__((__noreturn__)) static void Spawn(int os, const char *exe, int fd,
/* allocate extra bss */
a = p[i].p_vaddr + p[i].p_filesz;
a = (a + (PAGE_SIZE - 1)) & -PAGE_SIZE;
a = (a + (pagesz - 1)) & -pagesz;
b = p[i].p_vaddr + p[i].p_memsz;
if (b > a) {
rc = Mmap((void *)a, b - a, prot, flags | MAP_ANONYMOUS, 0, 0, os);
flags |= MAP_ANONYMOUS;
rc = Mmap((void *)(dynbase + a), b - a, prot, flags, -1, 0, os);
if (rc < 0) Pexit(os, exe, rc, "bss mmap");
}
}
/* finish up */
if (!found_entry) {
Pexit(os, exe, 0, "ELF entrypoint not found in PT_LOAD with PF_X");
}
Close(fd, os);
Msyscall(code, codesize, os);
Msyscall(dynbase + code, codesize, os);
/* we clear all the general registers we can to have some wiggle room
to extend the behavior of this loader in the future. we don't need
to clear the xmm registers since the ape loader should be compiled
with the -mgeneral-regs-only flag. */
asm volatile("xor\t%%eax,%%eax\n\t"
"xor\t%%r8d,%%r8d\n\t"
"xor\t%%r9d,%%r9d\n\t"
"xor\t%%r10d,%%r10d\n\t"
"xor\t%%r11d,%%r11d\n\t"
"xor\t%%ebx,%%ebx\n\t" /* netbsd doesnt't clear this */
"xor\t%%r12d,%%r12d\n\t" /* netbsd doesnt't clear this */
"xor\t%%r13d,%%r13d\n\t" /* netbsd doesnt't clear this */
"xor\t%%r14d,%%r14d\n\t" /* netbsd doesnt't clear this */
"xor\t%%r15d,%%r15d\n\t" /* netbsd doesnt't clear this */
"mov\t%%rdx,%%rsp\n\t"
"xor\t%%edx,%%edx\n\t"
"push\t%%rsi\n\t"
"xor\t%%esi,%%esi\n\t"
"xor\t%%ebp,%%ebp\n\t"
"ret"
: /* no outputs */
: "D"(IsFreebsd() ? sp : 0), "S"(e->e_entry), "d"(sp), "c"(os)
: "memory");
__builtin_unreachable();
/* call program entrypoint */
Launch(IsFreebsd() ? sp : 0, dynbase + e->e_entry, sp, os);
}
static void TryElf(struct ApeLoader *M, const char *exe, int fd, long *sp,
long *auxv, int os) {
unsigned size = M->ehdr.ehdr.e_phnum;
if (Read32(M->ehdr.buf) == Read32("\177ELF") &&
M->ehdr.ehdr.e_type == ET_EXEC &&
M->ehdr.ehdr.e_machine == EM_NEXGEN32E &&
M->ehdr.ehdr.e_ident[EI_CLASS] != ELFCLASS32 &&
M->ehdr.ehdr.e_phentsize >= sizeof(M->phdr.phdr) &&
(size *= M->ehdr.ehdr.e_phentsize) <= sizeof(M->phdr.buf) &&
Pread(fd, M->phdr.buf, size, M->ehdr.ehdr.e_phoff, os) == size) {
for (; *auxv; auxv += 2) {
switch (*auxv) {
case AT_PHDR:
auxv[1] = (unsigned long)&M->phdr;
break;
case AT_PHENT:
auxv[1] = M->ehdr.ehdr.e_phentsize;
break;
case AT_PHNUM:
auxv[1] = M->ehdr.ehdr.e_phnum;
break;
default:
break;
}
}
Spawn(os, exe, fd, sp, &M->ehdr.ehdr, &M->phdr.phdr);
static const char *TryElf(struct ApeLoader *M, const char *exe, int fd,
long *sp, long *auxv, unsigned long pagesz, int os) {
long rc;
unsigned size;
if (READ32(M->ehdr.buf) != READ32("\177ELF")) {
return "didn't embed ELF magic";
}
if (M->ehdr.ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
return "32-bit ELF isn't supported";
}
if (M->ehdr.ehdr.e_type != ET_EXEC && M->ehdr.ehdr.e_type != ET_DYN) {
return "ELF not ET_EXEC or ET_DYN";
}
if (M->ehdr.ehdr.e_machine != EM_NEXGEN32E) {
return "couldn't find ELF header with x86-64 machine type";
}
if (M->ehdr.ehdr.e_phentsize < sizeof(M->phdr.phdr)) {
return "e_phentsize is too small";
}
size = M->ehdr.ehdr.e_phnum;
if ((size *= M->ehdr.ehdr.e_phentsize) > sizeof(M->phdr.buf)) {
return "too many ELF program headers";
}
rc = Pread(fd, M->phdr.buf, size, M->ehdr.ehdr.e_phoff, os);
if (rc < 0) return "failed to read ELF program headers";
if (rc != size) return "truncated read of ELF program headers";
for (; *auxv; auxv += 2) {
switch (*auxv) {
case AT_PHDR:
auxv[1] = (unsigned long)&M->phdr;
break;
case AT_PHENT:
auxv[1] = M->ehdr.ehdr.e_phentsize;
break;
case AT_PHNUM:
auxv[1] = M->ehdr.ehdr.e_phnum;
break;
default:
break;
}
}
Spawn(os, exe, fd, sp, pagesz, &M->ehdr.ehdr, &M->phdr.phdr);
}
static __attribute__((__noreturn__)) void ShowUsage(int os, int fd, int rc) {
Print(os, fd,
"NAME\n"
"\n"
" actually portable executable loader v1.3\n"
" copyright 2023 justine alexandra roberts tunney\n"
" https://justine.lol/ape.html\n"
"\n"
"USAGE\n"
"\n"
" ape [FLAGS] PROG [ARGV1,ARGV2,...]\n"
" ape [FLAGS] - PROG [ARGV0,ARGV1,...]\n"
"\n"
"FLAGS\n"
"\n"
" -h show this help\n"
" -f force loading of program (do not use execve)\n"
"\n",
0l);
Exit(rc, os);
}
__attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) {
int rc;
unsigned i, n;
int usetheforce;
int c, fd, os, argc;
struct ApeLoader *M;
unsigned long pagesz;
long *auxv, *ap, *ew;
char *p, *exe, *prog, **argv, **envp;
char *p, *pe, *exe, *ape, *prog, **argv, **envp;
(void)Utox;
/* detect freebsd */
if (SupportsXnu() && dl == XNU) {
@ -699,14 +789,21 @@ __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) {
}
}
/* determine ape loader program name */
ape = argv[0];
if (!ape) ape = "ape";
/* detect openbsd */
if (SupportsOpenbsd() && !os && !auxv[0]) {
os = OPENBSD;
}
/* detect netbsd and find end of words */
pagesz = 0;
for (ap = auxv; ap[0]; ap += 2) {
if (SupportsNetbsd() && !os && ap[0] == AT_EXECFN_NETBSD) {
if (ap[0] == AT_PAGESZ) {
pagesz = ap[1];
} else if (SupportsNetbsd() && !os && ap[0] == AT_EXECFN_NETBSD) {
os = NETBSD;
}
}
@ -723,6 +820,23 @@ __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) {
os = LINUX;
}
/* parse flags */
usetheforce = 0;
while (argc > 1) {
if (argv[1][0] != '-') break; /* normal argument */
if (!argv[1][1]) break; /* hyphen argument */
if (!StrCmp(argv[1], "-h") || !StrCmp(argv[1], "--help")) {
ShowUsage(os, 1, 0);
} else if (!StrCmp(argv[1], "-f")) {
usetheforce = 1;
} else {
Print(os, 2, ape, ": invalid flag (pass -h for help)\n", 0l);
Exit(1, os);
}
*++sp = --argc;
++argv;
}
/* we can load via shell, shebang, or binfmt_misc */
if (argc >= 3 && !StrCmp(argv[1], "-")) {
/* if the first argument is a hyphen then we give the user the
@ -733,13 +847,7 @@ __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) {
argc = sp[3] = sp[0] - 3;
argv = (char **)((sp += 3) + 1);
} else if (argc < 2) {
Print(os, 2,
"usage: ape PROG [ARGV1,ARGV2,...]\n"
" ape - PROG [ARGV0,ARGV1,...]\n"
"αcτµαlly pδrταblε εxεcµταblε loader v1.2\n"
"copyright 2022 justine alexandra roberts tunney\n"
"https://justine.lol/ape.html\n",
0l);
Print(os, 2, ape, ": missing command name (pass -h for help)\n", 0l);
Exit(1, os);
} else {
prog = (char *)sp[2];
@ -749,14 +857,15 @@ __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) {
/* resolve path of executable and read its first page */
if (!(exe = Commandv(&M->ps, os, prog, GetEnv(envp, "PATH")))) {
Pexit(os, prog, 0, "not found (maybe chmod +x)");
Pexit(os, prog, 0, "not found (maybe chmod +x or ./ needed)");
} else if ((fd = Open(exe, O_RDONLY, 0, os)) < 0) {
Pexit(os, exe, fd, "open");
} else if ((rc = Pread(fd, M->ehdr.buf, sizeof(M->ehdr.buf), 0, os)) < 0) {
Pexit(os, exe, rc, "read");
} else if (rc != sizeof(M->ehdr.buf)) {
} else if ((unsigned long)rc < sizeof(M->ehdr.ehdr)) {
Pexit(os, exe, 0, "too small");
}
pe = M->ehdr.buf + rc;
/* change argv[0] to resolved path if it's ambiguous */
if (argc > 0 && *prog != '/' && *exe == '/' && !StrCmp(prog, argv[0])) {
@ -767,21 +876,24 @@ __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) {
1. if file is a native executable, try to run it natively
2. if ape, will scan shell script for elf printf statements
3. shell script may have multiple lines producing elf headers
4. all elf printf lines must exist in the first 4096 bytes of file
4. all elf printf lines must exist in the first 8192 bytes of file
5. elf program headers may appear anywhere in the binary */
if ((IsXnu() && Read32(M->ehdr.buf) == 0xFEEDFACE + 1) ||
(!IsXnu() && Read32(M->ehdr.buf) == Read32("\177ELF"))) {
if (!usetheforce &&
((IsXnu() && READ32(M->ehdr.buf) == 0xFEEDFACE + 1) ||
(!IsXnu() && READ32(M->ehdr.buf) == READ32("\177ELF")))) {
Close(fd, os);
Execve(exe, argv, envp, os);
if ((fd = Open(exe, O_RDONLY, 0, os)) < 0) {
Pexit(os, exe, rc, "execve and open failed");
}
}
if (Read64(M->ehdr.buf) == Read64("MZqFpD='") ||
Read64(M->ehdr.buf) == Read64("jartsr='")) {
for (p = M->ehdr.buf; p < M->ehdr.buf + sizeof(M->ehdr.buf); ++p) {
if (Read64(p) != Read64("printf '")) {
if (READ64(M->ehdr.buf) == READ64("MZqFpD='") ||
READ64(M->ehdr.buf) == READ64("jartsr='")) {
for (p = M->ehdr.buf; p < pe; ++p) {
if (READ64(p) != READ64("printf '")) {
continue;
}
for (i = 0, p += 8;
p + 3 < M->ehdr.buf + sizeof(M->ehdr.buf) && (c = *p++) != '\'';) {
for (i = 0, p += 8; p + 3 < pe && (c = *p++) != '\'';) {
if (c == '\\') {
if ('0' <= *p && *p <= '7') {
c = *p++ - '0';
@ -796,12 +908,14 @@ __attribute__((__noreturn__)) void ApeLoader(long di, long *sp, char dl) {
}
}
M->ehdr.buf[i++] = c;
if (i >= sizeof(M->ehdr.buf)) {
break;
}
}
if (i >= sizeof(M->ehdr.ehdr)) {
TryElf(M, exe, fd, sp, auxv, os);
TryElf(M, exe, fd, sp, auxv, pagesz, os);
}
}
}
TryElf(M, exe, fd, sp, auxv, os);
Pexit(os, exe, 0, "Not an acceptable APE/ELF executable for x86-64");
Pexit(os, exe, 0, TryElf(M, exe, fd, sp, auxv, pagesz, os));
}