/*-*- 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 2022 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/assert.h" #include "libc/calls/blockcancel.internal.h" #include "libc/calls/calls.h" #include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" #include "libc/calls/struct/sigset.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/kprintf.h" #include "libc/intrin/safemacros.internal.h" #include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" #include "libc/limits.h" #include "libc/proc/execve.internal.h" #include "libc/str/str.h" #include "libc/sysv/consts/f.h" #include "libc/sysv/consts/fd.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]; return (sys_pread(fd, buf, 8, 0, 0) == 8) && IsApeLoadable(buf); } 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; } /** * Creates a memfd and copies fd to it. * * This does an inplace conversion of APE to ELF when detected!!!! */ static int fd_to_mem_fd(const int infd, char *path) { if ((!IsLinux() && !IsFreebsd()) || !_weaken(mmap) || !_weaken(munmap)) { return enosys(); } struct stat st; if (fstat(infd, &st) == -1) { return -1; } int fd; if (IsLinux()) { fd = sys_memfd_create(__func__, MFD_CLOEXEC); } else if (IsFreebsd()) { fd = sys_shm_open(SHM_ANON, O_CREAT | O_RDWR, 0); } else { return enosys(); } if (fd == -1) { return -1; } void *space; if ((sys_ftruncate(fd, st.st_size, st.st_size) != -1) && ((space = _weaken(mmap)(0, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) != MAP_FAILED)) { ssize_t readRc; readRc = pread(infd, space, st.st_size, 0); bool success = readRc != -1; 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) && ape_to_elf(space, st.st_size))) { const int newfd = fcntl(fd, F_DUPFD, 9001); if (newfd != -1) { close(fd); fd = newfd; } } } const int e = errno; if ((_weaken(munmap)(space, st.st_size) != -1) && success) { if (path) { FormatInt32(stpcpy(path, "COSMOPOLITAN_INIT_ZIPOS="), fd); } 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 are * supported. Zipos is supported. Zipos fds or FD_CLOEXEC APE fds or * fds that fail fexecve with ENOEXEC are copied to a new memfd (with * in-memory APE to ELF conversion) and fexecve is (re)attempted. * * @param fd is opened executable and current file position is ignored * @return doesn't return on success, otherwise -1 w/ errno * @raise ENOEXEC if file at `fd` isn't an assimilated ELF executable * @raise ENOSYS on Windows, XNU, OpenBSD, NetBSD, and Metal */ int fexecve(int fd, char *const argv[], char *const envp[]) { int rc = 0; if (!argv || !envp || (IsAsan() && (!__asan_is_valid_strlist(argv) || !__asan_is_valid_strlist(envp)))) { rc = efault(); } else { STRACE("fexecve(%d, %s, %s) → ...", fd, DescribeStringList(argv), DescribeStringList(envp)); int savedErr = 0; do { if (!IsLinux() && !IsFreebsd()) { rc = enosys(); break; } if (!__isfdkind(fd, kFdZip)) { bool memfdReq; BLOCK_SIGNALS; BLOCK_CANCELATION; strace_enabled(-1); memfdReq = ((rc = fcntl(fd, F_GETFD)) != -1) && (rc & FD_CLOEXEC) && IsAPEFd(fd); strace_enabled(+1); ALLOW_CANCELATION; ALLOW_SIGNALS; if (rc == -1) { break; } else if (!memfdReq) { fexecve_impl(fd, argv, envp); if (errno != ENOEXEC) { break; } savedErr = ENOEXEC; } } int newfd; char *path = alloca(PATH_MAX); BLOCK_SIGNALS; BLOCK_CANCELATION; strace_enabled(-1); newfd = fd_to_mem_fd(fd, path); strace_enabled(+1); ALLOW_CANCELATION; ALLOW_SIGNALS; if (newfd == -1) { break; } size_t numenvs; for (numenvs = 0; envp[numenvs];) ++numenvs; // const size_t desenvs = min(500, max(numenvs + 1, 2)); static _Thread_local char *envs[500]; memcpy(envs, envp, numenvs * sizeof(char *)); envs[numenvs] = path; envs[numenvs + 1] = NULL; fexecve_impl(newfd, argv, envs); if (!savedErr) { savedErr = errno; } BLOCK_SIGNALS; BLOCK_CANCELATION; strace_enabled(-1); close(newfd); strace_enabled(+1); ALLOW_CANCELATION; ALLOW_SIGNALS; } while (0); if (savedErr) { errno = savedErr; } rc = -1; } STRACE("fexecve(%d) failed %d% m", fd, rc); return rc; }