diff --git a/libc/calls/execve-sysv.c b/libc/calls/execve-sysv.c index 191b863e1..4302fec5f 100644 --- a/libc/calls/execve-sysv.c +++ b/libc/calls/execve-sysv.c @@ -19,6 +19,7 @@ #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/syscall-sysv.internal.h" #include "libc/dce.h" #include "libc/errno.h" @@ -37,6 +38,11 @@ static bool CanExecute(const char *path) { return !sys_faccessat(AT_FDCWD, path, X_OK, 0); } +bool IsAPEMagic(char buf[8]) { + return READ64LE(buf) == READ64LE("MZqFpD='") || + READ64LE(buf) == READ64LE("JTqFpD='"); +} + static bool IsApeBinary(const char *path) { int fd; char buf[8]; @@ -44,11 +50,7 @@ static bool IsApeBinary(const char *path) { // TODO(jart): Should we block signals too? BLOCK_CANCELLATIONS; if ((fd = sys_open(path, O_RDONLY, 0)) != -1) { - if (sys_read(fd, buf, 8) == 8 && // - (READ64LE(buf) == READ64LE("MZqFpD='") || - READ64LE(buf) == READ64LE("JTqFpD='"))) { - res = true; - } + res = sys_read(fd, buf, 8) == 8 && IsAPEMagic(buf); sys_close(fd); } ALLOW_CANCELLATIONS; diff --git a/libc/calls/execve-sysv.internal.h b/libc/calls/execve-sysv.internal.h new file mode 100644 index 000000000..b9148c5e4 --- /dev/null +++ b/libc/calls/execve-sysv.internal.h @@ -0,0 +1,10 @@ +#ifndef COSMOPOLITAN_LIBC_CALLS_EXECVE_SYSV_H_ +#define COSMOPOLITAN_LIBC_CALLS_EXECVE_SYSV_H_ +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ + +bool IsAPEMagic(char[8]) _Hide; + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_LIBC_CALLS_EXECVE_SYSV_H_ */ diff --git a/libc/calls/fexecve.c b/libc/calls/fexecve.c index 19b30a019..d75d35d55 100644 --- a/libc/calls/fexecve.c +++ b/libc/calls/fexecve.c @@ -16,21 +16,158 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/calls/calls.h" +#include "libc/calls/blockcancel.internal.h" +#include "libc/calls/cp.internal.h" +#include "libc/calls/execve-sysv.internal.h" +#include "libc/calls/struct/stat.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" +#include "libc/errno.h" #include "libc/fmt/itoa.h" #include "libc/intrin/asan.internal.h" #include "libc/intrin/describeflags.internal.h" #include "libc/intrin/strace.internal.h" +#include "libc/intrin/weaken.h" #include "libc/str/str.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/mfd.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/prot.h" +#include "libc/sysv/consts/shm.h" #include "libc/sysv/errfuns.h" +static bool IsAPEFd(const int fd) { + char buf[8]; + bool res; + // TODO(jart): Should we block signals too? + BLOCK_CANCELLATIONS; + res = (sys_pread(fd, buf, 8, 0, 0) == 8) && IsAPEMagic(buf); + ALLOW_CANCELLATIONS; + return res; +} + +static int fexecve_impl(const int fd, char *const argv[], char *const envp[]) { + int rc; + if (IsLinux()) { + char path[14 + 12]; + FormatInt32(stpcpy(path, "/proc/self/fd/"), fd); + rc = __sys_execve(path, argv, envp); + } else if (IsFreebsd()) { + rc = sys_fexecve(fd, argv, envp); + } else { + rc = enosys(); + } + return rc; +} + +typedef enum { + PTF_NUM = 1 << 0, + PTF_NUM2 = 1 << 1, + PTF_NUM3 = 1 << 2, + PTF_ANY = 1 << 3 +} PTF_PARSE; + +static bool ape_to_elf(void *ape, const size_t apesize) { + static const char printftok[] = "printf '"; + static const size_t printftoklen = sizeof(printftok)-1; + const char *tok = memmem(ape, apesize, printftok, printftoklen); + if(tok) { + tok += printftoklen; + uint8_t *dest = ape; + PTF_PARSE state = PTF_ANY; + uint8_t value = 0; + for(;tok < (const char*)(dest+apesize); tok++) { + if((state & (PTF_NUM | PTF_NUM2 | PTF_NUM3)) && (*tok >= '0' && *tok <= '7')) { + value = (value << 3) | (*tok - '0'); + state <<= 1; + if(state & PTF_ANY) { + *dest++ = value; + } + } + else if(state & PTF_NUM) { + break; + } + else { + if(state & (PTF_NUM2 | PTF_NUM3)) { + *dest++ = value; + } + if(*tok == '\\') { + state = PTF_NUM; + value = 0; + } else if(*tok == '\'') { + return true; + } + else { + *dest++ = *tok; + state = PTF_ANY; + } + } + } + } + errno = ENOEXEC; + return false; +} + +static int ape_fd_to_mem_elf_fd(const int infd, char *path) { + if (!IsLinux() && !IsFreebsd()) { + return enosys(); + } + + struct stat st; + if(sys_fstat(infd, &st) == -1) { + return -1; + } + int fd; + if (IsLinux()) { + fd = sys_memfd_create(__func__, MFD_CLOEXEC); + if ((fd != -1) && path) { + FormatInt32(stpcpy(path, "/proc/self/fd/"), fd); + } + } else if (IsFreebsd()) { + fd = sys_shm_open(SHM_ANON, O_CREAT | O_RDWR, 0); + } else { + return enosys(); + } + if (fd == -1) { + return -1; + } + void *space; + int rc; + BEGIN_CANCELLATION_POINT; + rc = sys_ftruncate(fd, st.st_size, st.st_size); + END_CANCELLATION_POINT; + if ((rc != -1) && + ((space = _weaken(mmap)(0, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, + 0)) != MAP_FAILED)) { + ssize_t readRc; + BEGIN_CANCELLATION_POINT; + readRc = sys_pread(infd, space, st.st_size, 0, 0); + END_CANCELLATION_POINT; + bool success = readRc != -1; + if (success && (st.st_size > 8) && IsAPEMagic(space)) { + success = ape_to_elf(space, st.st_size); + } + const int e = errno; + if ((_weaken(munmap)(space, st.st_size) != -1) && success) { + _unassert(readRc == st.st_size); + return fd; + } else if (!success) { + errno = e; + } + } + const int e = errno; + close(fd); + errno = e; + return -1; +} + /** * Executes binary executable at file descriptor. * - * This is only supported on Linux and FreeBSD. APE binaries currently - * aren't supported. + * This is only supported on Linux and FreeBSD. APE binaries are + * supported. * * @param fd is opened executable and current file position is ignored * @return doesn't return on success, otherwise -1 w/ errno @@ -39,7 +176,6 @@ */ int fexecve(int fd, char *const argv[], char *const envp[]) { int rc; - size_t i; if (!argv || !envp || (IsAsan() && (!__asan_is_valid_strlist(argv) || !__asan_is_valid_strlist(envp)))) { @@ -47,14 +183,12 @@ int fexecve(int fd, char *const argv[], char *const envp[]) { } else { STRACE("fexecve(%d, %s, %s) → ...", fd, DescribeStringList(argv), DescribeStringList(envp)); - if (IsLinux()) { - char path[14 + 12]; - FormatInt32(stpcpy(path, "/proc/self/fd/"), fd); - rc = __sys_execve(path, argv, envp); - } else if (IsFreebsd()) { - rc = sys_fexecve(fd, argv, envp); - } else { - rc = enosys(); + rc = fexecve_impl(fd, argv, envp); + if((errno == ENOEXEC) && (IsLinux() || IsFreebsd()) && IsAPEFd(fd)) { + const int newfd = ape_fd_to_mem_elf_fd(fd, NULL); + if(newfd != -1) { + rc = fexecve_impl(newfd, argv, envp); + } } } STRACE("fexecve(%d) failed %d% m", fd, rc); diff --git a/libc/calls/syscall-sysv.internal.h b/libc/calls/syscall-sysv.internal.h index 1eaa7df53..ea88f3159 100644 --- a/libc/calls/syscall-sysv.internal.h +++ b/libc/calls/syscall-sysv.internal.h @@ -105,6 +105,7 @@ i32 sys_setresuid(u32, u32, u32) _Hide; i32 sys_setreuid(u32, u32) _Hide; i32 sys_setsid(void) _Hide; i32 sys_setuid(i32) _Hide; +i32 sys_shm_open(const char *, i32, u32) _Hide; i32 sys_sigaction(i32, const void *, void *, i64, i64) _Hide; i32 sys_sigaltstack(const void *, void *) _Hide; i32 sys_symlinkat(const char *, i32, const char *) _Hide; diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index b8095d299..3cc40bfb5 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -1620,6 +1620,7 @@ syscon shm SHM_HUGETLB 0x0800 0 0 0 0 0 syscon shm SHM_LOCKED 0x0400 0 0 0 0 0 syscon shm SHM_NORESERVE 0x1000 0 0 0 0 0 syscon shm SHM_REMAP 0x4000 0 0 0 0 0 +syscon shm SHM_ANON 0 0 1 0 0 0 syscon lock LOCK_UNLOCK_CACHE 54 0 0 0 0 0 # wut diff --git a/libc/sysv/consts/SHM_ANON.s b/libc/sysv/consts/SHM_ANON.s new file mode 100644 index 000000000..e16543c5a --- /dev/null +++ b/libc/sysv/consts/SHM_ANON.s @@ -0,0 +1,2 @@ +.include "o/libc/sysv/consts/syscon.internal.inc" +.syscon shm,SHM_ANON,0,0,1,0,0,0 diff --git a/libc/sysv/consts/shm.h b/libc/sysv/consts/shm.h index 8d38d65db..24638978d 100644 --- a/libc/sysv/consts/shm.h +++ b/libc/sysv/consts/shm.h @@ -2,6 +2,7 @@ #define COSMOPOLITAN_LIBC_SYSV_CONSTS_SHM_H_ #include "libc/runtime/symbolic.h" +#define SHM_ANON SYMBOLIC(SHM_ANON) #define SHM_DEST SYMBOLIC(SHM_DEST) #define SHM_EXEC SYMBOLIC(SHM_EXEC) #define SHM_HUGETLB SYMBOLIC(SHM_HUGETLB) @@ -20,6 +21,7 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ +extern const char *SHM_ANON; extern const int SHM_DEST; extern const int SHM_EXEC; extern const int SHM_HUGETLB; diff --git a/test/libc/calls/fexecve_test.c b/test/libc/calls/fexecve_test.c index 08c451a92..ea51c8159 100644 --- a/test/libc/calls/fexecve_test.c +++ b/test/libc/calls/fexecve_test.c @@ -86,3 +86,14 @@ TEST(fexecve, memfd_create) { fexecve(fd, (char *const[]){0}, (char *const[]){0}); EXITS(42); } + +TEST(fexecve, APE) { + if (!IsLinux() && !IsFreebsd()) return; + testlib_extract("/zip/life-nomod.com", "life-nomod.com", 0555); + SPAWN(fork); + int fd = open("life-nomod.com", O_RDONLY); + ASSERT_NE(-1, fd); + if (fd == -1 && errno == ENOSYS) _Exit(42); + fexecve(fd, (char *const[]){0}, (char *const[]){0}); + EXITS(42); +} diff --git a/test/libc/calls/test.mk b/test/libc/calls/test.mk index cc44cbae0..46e9e36ee 100644 --- a/test/libc/calls/test.mk +++ b/test/libc/calls/test.mk @@ -93,6 +93,7 @@ o/$(MODE)/test/libc/calls/fexecve_test.com.dbg: \ o/$(MODE)/test/libc/calls/fexecve_test.o \ o/$(MODE)/test/libc/calls/calls.pkg \ o/$(MODE)/tool/build/echo.zip.o \ + o/$(MODE)/test/libc/calls/life-nomod.com.zip.o \ $(LIBC_TESTMAIN) \ $(CRT) \ $(APE_NO_MODIFY_SELF)