From 5923d483a4075c99d4858d505c3f425bd6d0e910 Mon Sep 17 00:00:00 2001 From: Gavin Hayes Date: Fri, 24 Feb 2023 14:48:24 -0500 Subject: [PATCH] Add execve / fexecve support to ZIpOS (#727) --- libc/calls/execve.c | 28 +++++++++---- libc/calls/fexecve.c | 76 +++++++++++++++++++++++++++------- test/libc/calls/execve_test.c | 26 ++++++++++++ test/libc/calls/fexecve_test.c | 34 +++++++++++++++ test/libc/calls/test.mk | 1 + 5 files changed, 143 insertions(+), 22 deletions(-) diff --git a/libc/calls/execve.c b/libc/calls/execve.c index 0ac77805b..7c0706a53 100644 --- a/libc/calls/execve.c +++ b/libc/calls/execve.c @@ -32,6 +32,7 @@ #include "libc/log/libfatal.internal.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/errfuns.h" +#include "libc/zipos/zipos.internal.h" /** * Replaces current process with program. @@ -51,6 +52,7 @@ * @vforksafe */ int execve(const char *prog, char *const argv[], char *const envp[]) { + struct ZiposUri uri; int rc; size_t i; if (!prog || !argv || !envp || @@ -61,16 +63,26 @@ int execve(const char *prog, char *const argv[], char *const envp[]) { } else { STRACE("execve(%#s, %s, %s) → ...", prog, DescribeStringList(argv), DescribeStringList(envp)); - if (!IsWindows()) { - rc = 0; - if (IsLinux() && __execpromises && _weaken(sys_pledge_linux)) { - rc = _weaken(sys_pledge_linux)(__execpromises, __pledge_mode); - } - if (!rc) { + rc = 0; + if (IsLinux() && __execpromises && _weaken(sys_pledge_linux)) { + rc = _weaken(sys_pledge_linux)(__execpromises, __pledge_mode); + } + if (!rc) { + if (_weaken(__zipos_parseuri) && + (_weaken(__zipos_parseuri)(prog, &uri) != -1)) { + rc = _weaken(__zipos_open)(&uri, O_RDONLY | O_CLOEXEC, 0); + if (rc != -1) { + const int zipFD = rc; + strace_enabled(-1); + rc = fexecve(zipFD, argv, envp); + close(zipFD); + strace_enabled(+1); + } + } else if (!IsWindows()) { rc = sys_execve(prog, argv, envp); + } else { + rc = sys_execve_nt(prog, argv, envp); } - } else { - rc = sys_execve_nt(prog, argv, envp); } } STRACE("execve(%#s) failed %d% m", prog, rc); diff --git a/libc/calls/fexecve.c b/libc/calls/fexecve.c index 5943b4079..c85c65ae4 100644 --- a/libc/calls/fexecve.c +++ b/libc/calls/fexecve.c @@ -22,6 +22,7 @@ #include "libc/calls/calls.h" #include "libc/calls/cp.internal.h" #include "libc/calls/execve-sysv.internal.h" +#include "libc/calls/internal.h" #include "libc/calls/struct/stat.internal.h" #include "libc/calls/syscall-sysv.internal.h" #include "libc/dce.h" @@ -32,6 +33,8 @@ #include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.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" @@ -105,13 +108,18 @@ static bool ape_to_elf(void *ape, const size_t apesize) { return false; } -static int ape_fd_to_mem_elf_fd(const int infd, char *path) { +/** + * 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 (sys_fstat(infd, &st) == -1) { + if (fstat(infd, &st) == -1) { return -1; } int fd; @@ -133,7 +141,7 @@ static int ape_fd_to_mem_elf_fd(const int infd, char *path) { ((space = _weaken(mmap)(0, st.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)) != MAP_FAILED)) { ssize_t readRc; - readRc = sys_pread(infd, space, st.st_size, 0, 0); + 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); @@ -156,7 +164,9 @@ static int ape_fd_to_mem_elf_fd(const int infd, char *path) { * Executes binary executable at file descriptor. * * This is only supported on Linux and FreeBSD. APE binaries are - * supported. + * 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 @@ -164,7 +174,7 @@ static int ape_fd_to_mem_elf_fd(const int infd, char *path) { * @raise ENOSYS on Windows, XNU, OpenBSD, NetBSD, and Metal */ int fexecve(int fd, char *const argv[], char *const envp[]) { - int rc; + int rc = 0; if (!argv || !envp || (IsAsan() && (!__asan_is_valid_strlist(argv) || !__asan_is_valid_strlist(envp)))) { @@ -172,22 +182,60 @@ int fexecve(int fd, char *const argv[], char *const envp[]) { } else { STRACE("fexecve(%d, %s, %s) → ...", fd, DescribeStringList(argv), DescribeStringList(envp)); - rc = fexecve_impl(fd, argv, envp); - if ((errno == ENOEXEC) && (IsLinux() || IsFreebsd())) { - int newfd = -1; + do { + if (!IsLinux() && !IsFreebsd()) { + rc = enosys(); + break; + } + if (!__isfdkind(fd, kFdZip)) { + bool memfdReq; + BEGIN_CANCELLATION_POINT; + BLOCK_SIGNALS; + strace_enabled(-1); + memfdReq = ((rc = fcntl(fd, F_GETFD)) != -1) && (rc & FD_CLOEXEC) && + IsAPEFd(fd); + strace_enabled(+1); + ALLOW_SIGNALS; + END_CANCELLATION_POINT; + if (rc == -1) { + break; + } else if (!memfdReq) { + rc = fexecve_impl(fd, argv, envp); + if (errno != ENOEXEC) { + break; + } + } + } + int newfd; BEGIN_CANCELLATION_POINT; BLOCK_SIGNALS; strace_enabled(-1); - if (IsAPEFd(fd)) { - newfd = ape_fd_to_mem_elf_fd(fd, NULL); - } + newfd = fd_to_mem_fd(fd, NULL); strace_enabled(+1); ALLOW_SIGNALS; END_CANCELLATION_POINT; - if (newfd != -1) { - rc = fexecve_impl(newfd, argv, envp); + if (newfd == -1) { + if (rc == -1) { + errno = ENOEXEC; + } + rc = -1; + break; } - } + fexecve_impl(newfd, argv, envp); + if (rc == -1) { + errno = ENOEXEC; + } + rc = -1; + const int savedErr = errno; + BEGIN_CANCELLATION_POINT; + BLOCK_SIGNALS; + strace_enabled(-1); + close(newfd); + strace_enabled(+1); + ALLOW_SIGNALS; + END_CANCELLATION_POINT; + errno = savedErr; + } while (0); } STRACE("fexecve(%d) failed %d% m", fd, rc); return rc; diff --git a/test/libc/calls/execve_test.c b/test/libc/calls/execve_test.c index f0ce111ea..6477ced0e 100644 --- a/test/libc/calls/execve_test.c +++ b/test/libc/calls/execve_test.c @@ -17,6 +17,8 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" +#include "libc/dce.h" +#include "libc/errno.h" #include "libc/fmt/conv.h" #include "libc/fmt/itoa.h" #include "libc/runtime/runtime.h" @@ -58,3 +60,27 @@ TEST(execve, testArgPassing) { EXITS(0); } } + +TEST(execve, ziposELF) { + if (!IsLinux() && !IsFreebsd()) { + EXPECT_SYS(ENOSYS, -1, + execve("/zip/life.elf", (char *const[]){0}, (char *const[]){0})); + return; + } + SPAWN(fork); + execve("/zip/life.elf", (char *const[]){0}, (char *const[]){0}); + notpossible; + EXITS(42); +} + +TEST(execve, ziposAPE) { + if (!IsLinux()) { + EXPECT_EQ(-1, execve("/zip/life-nomod.com", (char *const[]){0}, + (char *const[]){0})); + return; + } + SPAWN(fork); + execve("/zip/life-nomod.com", (char *const[]){0}, (char *const[]){0}); + notpossible; + EXITS(42); +} diff --git a/test/libc/calls/fexecve_test.c b/test/libc/calls/fexecve_test.c index ea51c8159..b25b8073e 100644 --- a/test/libc/calls/fexecve_test.c +++ b/test/libc/calls/fexecve_test.c @@ -27,6 +27,8 @@ #include "libc/testlib/testlib.h" // clang-format off +STATIC_YOINK("zip_uri_support"); + int fds[2]; char buf[8]; char testlib_enable_tmp_setup_teardown; @@ -97,3 +99,35 @@ TEST(fexecve, APE) { fexecve(fd, (char *const[]){0}, (char *const[]){0}); EXITS(42); } + +TEST(fexecve, APE_cloexec) { + if (!IsLinux() && !IsFreebsd()) return; + testlib_extract("/zip/life-nomod.com", "life-nomod.com", 0555); + SPAWN(fork); + int fd = open("life-nomod.com", O_RDONLY | O_CLOEXEC); + ASSERT_NE(-1, fd); + if (fd == -1 && errno == ENOSYS) _Exit(42); + fexecve(fd, (char *const[]){0}, (char *const[]){0}); + EXITS(42); +} + +TEST(fexecve, zipos) { + if (!IsLinux() && !IsFreebsd()) return; + int fd = open("/zip/life.elf", O_RDONLY); + SPAWN(fork); + if (fd == -1 && errno == ENOSYS) _Exit(42); + fexecve(fd, (char *const[]){0}, (char *const[]){0}); + EXITS(42); + close(fd); +} + +TEST(fexecve, ziposAPE) { + if (!IsLinux() && !IsFreebsd()) return; + int fd = open("/zip/life-nomod.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 46e9e36ee..ddd542b07 100644 --- a/test/libc/calls/test.mk +++ b/test/libc/calls/test.mk @@ -92,6 +92,7 @@ o/$(MODE)/test/libc/calls/fexecve_test.com.dbg: \ $(TEST_LIBC_CALLS_DEPS) \ o/$(MODE)/test/libc/calls/fexecve_test.o \ o/$(MODE)/test/libc/calls/calls.pkg \ + 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 \ $(LIBC_TESTMAIN) \