From 669b4c5f193f8e92a7741328942905714593705c Mon Sep 17 00:00:00 2001 From: Gavin Hayes Date: Wed, 29 Mar 2023 21:16:46 -0400 Subject: [PATCH] Add memfd fexecve zipos support (#752) --- libc/calls/fexecve.c | 49 +++++++++++++++++++++++----------- libc/zipos/get.c | 16 ++++++++--- test/libc/calls/fexecve_test.c | 11 ++++++++ test/libc/calls/test.mk | 7 +++-- test/libc/calls/zipread.c | 41 ++++++++++++++++++++++++++++ 5 files changed, 102 insertions(+), 22 deletions(-) create mode 100644 test/libc/calls/zipread.c diff --git a/libc/calls/fexecve.c b/libc/calls/fexecve.c index c85c65ae4..c2b5b208c 100644 --- a/libc/calls/fexecve.c +++ b/libc/calls/fexecve.c @@ -30,6 +30,7 @@ #include "libc/fmt/itoa.h" #include "libc/intrin/asan.internal.h" #include "libc/intrin/describeflags.internal.h" +#include "libc/intrin/safemacros.internal.h" #include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" #include "libc/str/str.h" @@ -125,9 +126,6 @@ static int fd_to_mem_fd(const int infd, char *path) { 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 { @@ -144,10 +142,22 @@ static int fd_to_mem_fd(const int infd, char *path) { readRc = pread(infd, space, st.st_size, 0); bool success = readRc != -1; if (success && (st.st_size > 8) && IsAPEMagic(space)) { - success = ape_to_elf(space, st.st_size); + 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) { @@ -182,6 +192,7 @@ int fexecve(int fd, char *const argv[], char *const envp[]) { } else { STRACE("fexecve(%d, %s, %s) → ...", fd, DescribeStringList(argv), DescribeStringList(envp)); + int savedErr = 0; do { if (!IsLinux() && !IsFreebsd()) { rc = enosys(); @@ -200,33 +211,36 @@ int fexecve(int fd, char *const argv[], char *const envp[]) { if (rc == -1) { break; } else if (!memfdReq) { - rc = fexecve_impl(fd, argv, envp); + fexecve_impl(fd, argv, envp); if (errno != ENOEXEC) { break; } + savedErr = ENOEXEC; } } int newfd; + char *path = alloca(PATH_MAX); BEGIN_CANCELLATION_POINT; BLOCK_SIGNALS; strace_enabled(-1); - newfd = fd_to_mem_fd(fd, NULL); + newfd = fd_to_mem_fd(fd, path); strace_enabled(+1); ALLOW_SIGNALS; END_CANCELLATION_POINT; if (newfd == -1) { - if (rc == -1) { - errno = ENOEXEC; - } - rc = -1; break; } - fexecve_impl(newfd, argv, envp); - if (rc == -1) { - errno = ENOEXEC; + 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; } - rc = -1; - const int savedErr = errno; BEGIN_CANCELLATION_POINT; BLOCK_SIGNALS; strace_enabled(-1); @@ -234,8 +248,11 @@ int fexecve(int fd, char *const argv[], char *const envp[]) { strace_enabled(+1); ALLOW_SIGNALS; END_CANCELLATION_POINT; - errno = savedErr; } while (0); + if (savedErr) { + errno = savedErr; + } + rc = -1; } STRACE("fexecve(%d) failed %d% m", fd, rc); return rc; diff --git a/libc/zipos/get.c b/libc/zipos/get.c index 47b4bc26d..395fd3b86 100644 --- a/libc/zipos/get.c +++ b/libc/zipos/get.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/calls/metalfile.internal.h" +#include "libc/fmt/conv.h" #include "libc/intrin/cmpxchg.h" #include "libc/intrin/promises.internal.h" #include "libc/intrin/strace.internal.h" @@ -59,7 +60,7 @@ static void __zipos_munmap_unneeded(const uint8_t *base, const uint8_t *cdir, * @threadsafe */ struct Zipos *__zipos_get(void) { - int fd; + int fd = -1; ssize_t size; const char *msg; static bool once; @@ -67,10 +68,17 @@ struct Zipos *__zipos_get(void) { const char *progpath; static struct Zipos zipos; uint8_t *map, *base, *cdir; - if (!once && PLEDGED(RPATH)) { + progpath = getenv("COSMOPOLITAN_INIT_ZIPOS"); + if (progpath) { + fd = atoi(progpath); + } + if (!once && ((fd != -1) || PLEDGED(RPATH))) { __zipos_lock(); - progpath = GetProgramExecutableName(); - if ((fd = open(progpath, O_RDONLY)) != -1) { + if (fd == -1) { + progpath = GetProgramExecutableName(); + fd = open(progpath, O_RDONLY); + } + if (fd != -1) { if ((size = getfiledescriptorsize(fd)) != -1ul && (map = mmap(0, size, PROT_READ, MAP_SHARED, fd, 0)) != MAP_FAILED) { if ((base = FindEmbeddedApe(map, size))) { diff --git a/test/libc/calls/fexecve_test.c b/test/libc/calls/fexecve_test.c index 69b4ba5fa..4eab341fb 100644 --- a/test/libc/calls/fexecve_test.c +++ b/test/libc/calls/fexecve_test.c @@ -128,3 +128,14 @@ TEST(fexecve, ziposAPE) { EXITS(42); close(fd); } + +TEST(fexecve, ziposAPEHasZipos) { + if (!IsLinux() && !IsFreebsd()) return; + int fd = open("/zip/zipread.com", O_RDONLY); + SPAWN(fork); + ASSERT_NE(-1, fd); + if (fd == -1 && errno == ENOSYS) _Exit(42); + fexecve(fd, (char *const[]){0}, (char *const[]){0}); + EXITS(42); + close(fd); +} diff --git a/test/libc/calls/test.mk b/test/libc/calls/test.mk index 3dbad448a..21311d7c0 100644 --- a/test/libc/calls/test.mk +++ b/test/libc/calls/test.mk @@ -19,7 +19,8 @@ TEST_LIBC_CALLS_BINS = \ $(TEST_LIBC_CALLS_COMS) \ $(TEST_LIBC_CALLS_COMS:%=%.dbg) \ o/$(MODE)/test/libc/calls/life-nomod.com \ - o/$(MODE)/test/libc/calls/life-classic.com + o/$(MODE)/test/libc/calls/life-classic.com \ + o/$(MODE)/test/libc/calls/zipread.com TEST_LIBC_CALLS_TESTS = \ $(TEST_LIBC_CALLS_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) @@ -95,6 +96,7 @@ o/$(MODE)/test/libc/calls/fexecve_test.com.dbg: \ o/$(MODE)/test/libc/mem/prog/life.elf.zip.o \ o/$(MODE)/tool/build/echo.zip.o \ o/$(MODE)/test/libc/calls/life-nomod.com.zip.o \ + o/$(MODE)/test/libc/calls/zipread.com.zip.o \ $(LIBC_TESTMAIN) \ $(CRT) \ $(APE_NO_MODIFY_SELF) @@ -102,7 +104,8 @@ o/$(MODE)/test/libc/calls/fexecve_test.com.dbg: \ o/$(MODE)/test/libc/calls/tiny64.elf.zip.o \ o/$(MODE)/test/libc/calls/life-nomod.com.zip.o \ -o/$(MODE)/test/libc/calls/life-classic.com.zip.o: private \ +o/$(MODE)/test/libc/calls/life-classic.com.zip.o \ +o/$(MODE)/test/libc/calls/zipread.com.zip.o: private \ ZIPOBJ_FLAGS += \ -B diff --git a/test/libc/calls/zipread.c b/test/libc/calls/zipread.c new file mode 100644 index 000000000..06c911c36 --- /dev/null +++ b/test/libc/calls/zipread.c @@ -0,0 +1,41 @@ +/*-*- 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 Gavin Arthur Hayes │ +│ │ +│ 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/str/str.h" +#include "libc/sysv/consts/o.h" + +STATIC_YOINK("zip_uri_support"); + +int main(int argc, char *argv[]) { + int fd = open("/zip/life.elf", O_RDONLY); + if (fd != -1) { + uint8_t buf[4] = {0}; + ssize_t readres = read(fd, buf, sizeof(buf)); + if (readres == sizeof(buf)) { + if (memcmp(buf, + "\x7F" + "ELF", + sizeof(buf)) == 0) { + return 42; + } + } + close(fd); + } + return 1; +}