Improve AARCH64 execution

This change fixes bugs in the APE loader. The execve() unit tests are
now enabled for MODE=aarch64. See the README for how you need to have
binfmt_misc configured with Qemu to run them. Apple Silicon bugs have
been fixed too, e.g. tkill() now works.
This commit is contained in:
Justine Tunney 2023-09-11 13:51:37 -07:00
parent 1965d7488e
commit 77a7873057
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
31 changed files with 599 additions and 195 deletions

View file

@ -59,6 +59,7 @@
#include "libc/sysv/errfuns.h"
#include "libc/thread/posixthread.internal.h"
#include "libc/thread/thread.h"
#ifdef __x86_64__
#define keywords textwindows dontasan dontubsan dontinstrument
@ -275,3 +276,5 @@ static keywords void sys_execve_nt_relay(intptr_t h, long b, long c, long d) {
__imp_ExitProcess(dwExitCode);
__builtin_unreachable();
}
#endif /* __x86_64__ */

View file

@ -17,15 +17,21 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "ape/ape.h"
#include "libc/atomic.h"
#include "libc/calls/blockcancel.internal.h"
#include "libc/calls/blocksigs.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/execve-sysv.internal.h"
#include "libc/calls/cp.internal.h"
#include "libc/calls/execve.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
#include "libc/cosmo.h"
#include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/magnumstrs.internal.h"
#include "libc/intrin/bits.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/limits.h"
#include "libc/mem/alloca.h"
#include "libc/paths.h"
@ -36,77 +42,100 @@
#include "libc/sysv/consts/ok.h"
#include "libc/sysv/errfuns.h"
static bool CanExecute(const char *path) {
return !sys_faccessat(AT_FDCWD, path, X_OK, 0);
}
#define ELIBBAD_LINUX 80
#define EBADEXEC_XNU 85
#define EBADARCH_XNU 86
bool IsAPEMagic(char buf[8]) {
return READ64LE(buf) == READ64LE("MZqFpD='") ||
READ64LE(buf) == READ64LE("JTqFpD='");
}
static struct {
atomic_uint once;
const char *home;
const char *tmpdir;
} g_execve;
static bool IsApeBinary(const char *path) {
int fd;
char buf[8];
bool res = false;
// TODO(jart): Should we block signals too?
BLOCK_CANCELLATIONS;
if ((fd = sys_openat(AT_FDCWD, path, O_RDONLY, 0)) != -1) {
res = sys_read(fd, buf, 8) == 8 && IsAPEMagic(buf);
sys_close(fd);
static bool IsApeFile(const char *path) {
if (!endswith(path, ".com")) {
return true;
} else {
bool res = false;
BLOCK_CANCELLATIONS;
BEGIN_CANCELLATION_POINT;
int fd;
char buf[8];
int flags = O_RDONLY | O_NOCTTY | O_NONBLOCK | O_CLOEXEC;
if ((fd = sys_openat(AT_FDCWD, path, flags, 0)) != -1) {
res = sys_pread(fd, buf, 8, 0, 0) == 8 && IsApeLoadable(buf);
sys_close(fd);
}
END_CANCELLATION_POINT;
ALLOW_CANCELLATIONS;
return res;
}
ALLOW_CANCELLATIONS;
return res;
}
static const char *Join(const char *a, const char *b, char buf[PATH_MAX]) {
size_t n, m;
n = strlen(a);
m = strlen(b);
if (n + 1 + m + 1 < PATH_MAX) {
stpcpy(stpcpy(stpcpy(buf, a), "/"), b);
return buf;
} else {
return "";
if (a && *a) {
n = strlen(a);
m = strlen(b);
if (n + m + 1 < PATH_MAX) {
stpcpy(stpcpy(buf, a), b);
return buf;
}
}
return 0;
}
static void RetryExecve(const char *prog, char **argv, char *const *envp) {
if ((argv[0] = (char *)prog)) {
STRACE("execve(%#s, %s) due to %s", prog, DescribeStringList(argv),
_strerrno(errno));
__sys_execve(prog, argv, envp);
}
}
int sys_execve(const char *prog, char *const argv[], char *const envp[]) {
size_t i;
int e, rc;
char *buf;
char **shargs;
const char *ape;
e = errno;
__sys_execve(prog, argv, envp);
if (errno == ENOEXEC) {
for (i = 0; argv[i];) ++i;
buf = alloca(PATH_MAX);
shargs = alloca((i + 4) * sizeof(char *));
if (IsApeBinary(prog) &&
(CanExecute((ape = "/usr/bin/ape")) ||
CanExecute((ape = Join(firstnonnull(getenv("TMPDIR"),
firstnonnull(getenv("HOME"), ".")),
".ape-" APE_VERSION_STR, buf))) ||
CanExecute((ape = Join(firstnonnull(getenv("HOME"), "."),
".ape-" APE_VERSION_STR, buf))))) {
shargs[0] = (char *)ape;
shargs[1] = (char *)"-";
shargs[2] = (char *)prog;
memcpy(shargs + 3, argv, (i + 1) * sizeof(char *));
errno = e;
rc = __sys_execve(shargs[0], shargs, envp);
} else if (CanExecute(prog)) {
shargs[0] = _PATH_BSHELL;
shargs[1] = (char *)prog;
memcpy(shargs + 2, argv + 1, i * sizeof(char *));
errno = e;
rc = __sys_execve(shargs[0], shargs, envp);
} else {
rc = enoexec();
}
} else {
rc = -1;
}
return rc;
static void SetupExecve(void) {
g_execve.home = getenv("HOME");
g_execve.tmpdir = getenv("TMPDIR");
}
__attribute__((__constructor__)) static void InitExecve(void) {
cosmo_once(&g_execve.once, SetupExecve);
}
int sys_execve(const char *prog, char *const argv[], char *const envp[]) {
// try kernel
// this also checks execute bit
__sys_execve(prog, argv, envp);
if (!(errno == ENOEXEC || (IsLinux() && errno == ELIBBAD_LINUX))) {
return -1;
}
// allocate memory
int argc;
for (argc = 0; argv[argc];) ++argc;
char **shargs = alloca((argc + 4) * sizeof(char *));
// try ape loader
if (IsApeFile(prog)) {
shargs[1] = (char *)"-";
shargs[2] = (char *)prog;
memcpy(shargs + 3, argv, (argc + 1) * sizeof(char *));
RetryExecve("/usr/bin/ape", shargs, envp);
char *buf = alloca(PATH_MAX);
const char *name = "/.ape-" APE_VERSION_STR;
InitExecve();
RetryExecve(Join(g_execve.tmpdir, name, buf), shargs, envp);
RetryExecve(Join(g_execve.home, name, buf), shargs, envp);
RetryExecve(Join(".", name, buf), shargs, envp);
}
// try bourne shell
shargs[0] = _PATH_BSHELL;
shargs[1] = (char *)prog;
memcpy(shargs + 2, argv + 1, argc * sizeof(char *));
RetryExecve(shargs[0], shargs, envp);
enoexec();
return -1;
}

View file

@ -64,7 +64,7 @@ int execve(const char *prog, char *const argv[], char *const envp[]) {
!__asan_is_valid_strlist(envp)))) {
rc = efault();
} else {
STRACE("execve(%#s, %s, %s) → ...", prog, DescribeStringList(argv),
STRACE("execve(%#s, %s, %s)", prog, DescribeStringList(argv),
DescribeStringList(envp));
rc = 0;
if (IsLinux() && __execpromises && _weaken(sys_pledge_linux)) {

View file

@ -3,7 +3,9 @@
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
bool IsAPEMagic(char[8]);
void __execve_lock(void);
void __execve_unlock(void);
bool IsApeLoadable(char[8]);
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */

View file

@ -21,7 +21,7 @@
#include "libc/calls/blocksigs.internal.h"
#include "libc/calls/calls.h"
#include "libc/calls/cp.internal.h"
#include "libc/calls/execve-sysv.internal.h"
#include "libc/calls/execve.internal.h"
#include "libc/calls/internal.h"
#include "libc/calls/struct/stat.internal.h"
#include "libc/calls/syscall-sysv.internal.h"
@ -30,6 +30,7 @@
#include "libc/fmt/itoa.h"
#include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/safemacros.internal.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
@ -46,7 +47,7 @@
static bool IsAPEFd(const int fd) {
char buf[8];
return (sys_pread(fd, buf, 8, 0, 0) == 8) && IsAPEMagic(buf);
return (sys_pread(fd, buf, 8, 0, 0) == 8) && IsApeLoadable(buf);
}
static int fexecve_impl(const int fd, char *const argv[], char *const envp[]) {
@ -141,7 +142,7 @@ static int fd_to_mem_fd(const int infd, char *path) {
ssize_t readRc;
readRc = pread(infd, space, st.st_size, 0);
bool success = readRc != -1;
if (success && (st.st_size > 8) && IsAPEMagic(space)) {
if (success && (st.st_size > 8) && IsApeLoadable(space)) {
int flags = fcntl(fd, F_GETFD);
if ((success = (flags != -1) &&
(fcntl(fd, F_SETFD, flags & (~FD_CLOEXEC)) != -1) &&

View file

@ -93,9 +93,9 @@ static inline void GetProgramExecutableNameImpl(char *p, char *e) {
// if argv[0] exists then turn it into an absolute path. we also try
// adding a .com suffix since the ape auto-appends it when resolving
if (((q = __argv[0]) && !sys_faccessat(AT_FDCWD, q, F_OK, 0)) ||
((q = StrCat(u.path, __argv[0], ".com")) &&
!sys_faccessat(AT_FDCWD, q, F_OK, 0))) {
if ((q = __argv[0]) && ((!sys_faccessat(AT_FDCWD, q, F_OK, 0)) ||
((q = StrCat(u.path, __argv[0], ".com")) &&
!sys_faccessat(AT_FDCWD, q, F_OK, 0)))) {
if (*q != '/') {
if (q[0] == '.' && q[1] == '/') {
q += 2;

29
libc/calls/isapemagic.c Normal file
View file

@ -0,0 +1,29 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2023 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.
*/
#include "libc/calls/calls.h"
#include "libc/intrin/bits.h"
/**
* Returns true if executable image is supported by APE Loader.
*/
bool IsApeLoadable(char buf[8]) {
return READ32LE(buf) == READ32LE("\177ELF") ||
READ64LE(buf) == READ64LE("MZqFpD='") ||
READ64LE(buf) == READ64LE("JTqFpD='");
}

View file

@ -34,6 +34,7 @@
#include "libc/nt/struct/securityattributes.h"
#include "libc/nt/struct/startupinfo.h"
#include "libc/sysv/errfuns.h"
#ifdef __x86_64__
struct SpawnBlock {
union {
@ -108,3 +109,5 @@ textwindows int ntspawn(
if (handle) CloseHandle(handle);
return __fix_enotdir(rc, prog16);
}
#endif /* __x86_64__ */

View file

@ -32,6 +32,7 @@
#include "libc/nt/runtime.h"
#include "libc/nt/struct/context.h"
#include "libc/nt/thread.h"
#include "libc/runtime/syslib.internal.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/sysv/errfuns.h"
@ -97,10 +98,17 @@ static dontinline textwindows int __tkill_nt(int tid, int sig,
}
}
static int __tkill_m1(int tid, int sig, struct CosmoTib *tib) {
struct PosixThread *pt = (struct PosixThread *)__get_tls()->tib_pthread;
return __syslib->pthread_kill(pt->next, sig);
}
// OpenBSD has an optional `tib` parameter for extra safety.
int __tkill(int tid, int sig, void *tib) {
int rc;
if (IsLinux() || IsXnu() || IsFreebsd() || IsOpenbsd() || IsNetbsd()) {
if (IsXnuSilicon()) {
return __tkill_m1(tid, sig, tib);
} else if (IsLinux() || IsXnu() || IsFreebsd() || IsOpenbsd() || IsNetbsd()) {
rc = sys_tkill(tid, sig, tib);
} else if (IsWindows()) {
rc = __tkill_nt(tid, sig, tib);

View file

@ -406,11 +406,9 @@ static bool __asan_is_mapped(int x) {
// xxx: we can't lock because no reentrant locks yet
int i;
bool res;
struct MemoryIntervals *m;
__mmi_lock();
m = _weaken(_mmi);
i = __find_memory(m, x);
res = i < m->i && x >= m->p[i].x;
i = __find_memory(&_mmi, x);
res = i < _mmi.i && x >= _mmi.p[i].x;
__mmi_unlock();
return res;
}
@ -902,7 +900,7 @@ static __wur __asan_die_f *__asan_report(const void *addr, int size,
p = __asan_format_section(p, _etext, _edata, ".data", addr);
p = __asan_format_section(p, _end, _edata, ".bss", addr);
__mmi_lock();
for (m = _weaken(_mmi), i = 0; i < m->i; ++i) {
for (m = &_mmi, i = 0; i < m->i; ++i) {
x = m->p[i].x;
y = m->p[i].y;
p = __asan_format_interval(p, x << 16, (y << 16) + (FRAMESIZE - 1));
@ -1396,7 +1394,7 @@ void __asan_map_shadow(uintptr_t p, size_t n) {
kprintf("error: %p size %'zu overlaps shadow space\n", p, n);
_Exit(1);
}
m = _weaken(_mmi);
m = &_mmi;
a = (0x7fff8000 + (p >> 3)) >> 16;
b = (0x7fff8000 + (p >> 3) + (n >> 3) + 0xffff) >> 16;
for (; a <= b; a += i) {
@ -1413,12 +1411,11 @@ void __asan_map_shadow(uintptr_t p, size_t n) {
addr = (void *)ADDR_32_TO_48(a);
prot = PROT_READ | PROT_WRITE;
flag = MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS;
sm = _weaken(sys_mmap)(addr, size, prot, flag, -1, 0);
sm = sys_mmap(addr, size, prot, flag, -1, 0);
if (sm.addr == MAP_FAILED ||
_weaken(__track_memory)(m, a, a + i - 1, sm.maphandle,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, false,
false, 0, size) == -1) {
__track_memory(m, a, a + i - 1, sm.maphandle, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, false, false, 0,
size) == -1) {
kprintf("error: could not map asan shadow memory\n");
__asan_die()();
__asan_unreachable();
@ -1539,9 +1536,6 @@ void __asan_init(int argc, char **argv, char **envp, unsigned long *auxv) {
__write_str("error: asan binaries require windows10\r\n");
_Exit(0); /* So `make MODE=dbg test` passes w/ Windows7 */
}
REQUIRE(_mmi);
REQUIRE(sys_mmap);
REQUIRE(__track_memory);
if (_weaken(hook_malloc) || _weaken(hook_calloc) || _weaken(hook_realloc) ||
_weaken(hook_realloc_in_place) || _weaken(hook_free) ||
_weaken(hook_malloc_usable_size)) {

View file

@ -18,7 +18,6 @@
*/
#include "libc/intrin/describeflags.internal.h"
// TODO(jart): Fork this function into ASAN and non-ASAN versions.
const char *DescribeFlags(char *p, size_t n, const struct DescribeFlags *d,
size_t m, const char *prefix, unsigned x) {
bool t;

View file

@ -16,7 +16,6 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/assert.h"
#include "libc/runtime/memtrack.internal.h"
dontasan unsigned __find_memory(const struct MemoryIntervals *mm, int x) {
@ -31,6 +30,5 @@ dontasan unsigned __find_memory(const struct MemoryIntervals *mm, int x) {
r = m;
}
}
unassert(l == mm->i || x <= mm->p[l].y);
return l;
}

View file

@ -487,6 +487,10 @@ static errno_t CloneSilicon(int (*fn)(void *, int), char *stk, size_t stksz,
if (!(res = __syslib->pthread_create(&th, 0, SiliconThreadMain, wt)) &&
(flags & CLONE_PARENT_SETTID)) {
*ptid = tid;
if (flags & CLONE_SETTLS) {
struct CosmoTib *tib = tls;
tib[-1].tib_pthread = th;
}
}
return res;
}

View file

@ -16,7 +16,7 @@ COSMOPOLITAN_C_START_
*/
#define SYSLIB_MAGIC ('s' | 'l' << 8 | 'i' << 16 | 'b' << 24)
#define SYSLIB_VERSION 1
#define SYSLIB_VERSION 2
typedef uint64_t dispatch_time_t;
typedef uint64_t dispatch_semaphore_t;
@ -42,6 +42,8 @@ struct Syslib {
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);
/* v2 (2023-09-10) */
long (*pthread_self)(void);
};
extern struct Syslib *__syslib;

View file

@ -89,6 +89,8 @@ dos kNtErrorUnexpNetErr ECONNABORTED
dos kNtErrorWorkingSetQuota ENOMEM
dos kNtErrorWriteProtect EACCES
dos kNtErrorWrongDisk EACCES
dos kNtErrorExeMarkedInvalid ENOEXEC
dos kNtErrorExeMachineTypeMismatch ENOEXEC
dos WSAEACCES EACCES
dos WSAEDISCON EPIPE
dos WSAEFAULT EFAULT

View file

@ -0,0 +1,15 @@
// generated by libc/sysv/dos2errno.sh
#include "libc/nt/errors.h"
#ifndef __x86_64__
.end
#endif
.macro .e doscode systemv
.short \doscode
.long \systemv
.endm
.section .sort.rodata.dos2errno.2,"a",@progbits
.globl kDos2Errno.ENOEXEC
.type kDos2Errno.ENOEXEC,@object
kDos2Errno.ENOEXEC:
.e kNtErrorExeMarkedInvalid,ENOEXEC
.e kNtErrorExeMachineTypeMismatch,ENOEXEC

View file

@ -17,6 +17,8 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/calls/calls.h"
#include "libc/runtime/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/sysv/consts/o.h"
#include "libc/testlib/testlib.h"
@ -31,9 +33,24 @@ __static_yoink("zipos");
*/
void testlib_extract(const char *zip, const char *to, int mode) {
int fdin, fdout;
ASSERT_NE(-1, (fdin = open(zip, O_RDONLY)));
ASSERT_NE(-1, (fdout = creat(to, mode)));
ASSERT_NE(-1, copyfd(fdin, fdout, -1));
ASSERT_NE(-1, close(fdout));
ASSERT_NE(-1, close(fdin));
if ((fdin = open(zip, O_RDONLY)) == -1) {
perror(zip);
exit(1);
}
if ((fdout = creat(to, mode)) == -1) {
perror(to);
exit(1);
}
if (copyfd(fdin, fdout, -1) == -1) {
perror(zip);
exit(1);
}
if (close(fdout)) {
perror(to);
exit(1);
}
if (close(fdin)) {
perror(zip);
exit(1);
}
}

View file

@ -79,6 +79,7 @@ struct PosixThread {
char *tls; // bottom of tls allocation
struct CosmoTib *tib; // middle of tls allocation
struct Dll list; // list of threads
pthread_t next; // for xnu silicon
jmp_buf exiter; // for pthread_exit
pthread_attr_t attr;
struct _pthread_cleanup_buffer *cleanup;

View file

@ -78,6 +78,7 @@ static int PosixThread(void *arg, int tid) {
}
// set long jump handler so pthread_exit can bring control back here
if (!setjmp(pt->exiter)) {
pt->next = __get_tls()->tib_pthread;
__get_tls()->tib_pthread = (pthread_t)pt;
unassert(!sigprocmask(SIG_SETMASK, (sigset_t *)pt->attr.__sigmask, 0));
rc = pt->start(pt->arg);