From 893edc8983a3ec155ca01420789126eeb4a44702 Mon Sep 17 00:00:00 2001 From: Gavin Hayes Date: Wed, 16 Aug 2023 00:39:08 -0400 Subject: [PATCH 1/4] fexecve: renable and fix, assimilate via running shell script instead of running it fexecve_test.c: fix zipread.com. renable tinyelf test (x86_64) only fexecve_test.c: make memfd_create test work on all archs cosmopolitan is compiled to (f)execve_test.c add checking for /proc/self/fd/ for fexecve fexecve_test: get /zip/echo working again move execve test out of fexecve_test.c assimilate via running embedded shell script instead of parsing it satisfy -Werror=discarded-qualifiers fexecve: fix zipos when executable is not an APE comment out broken tests get most fexecve tests working again switch memfd_create test back to vfork add life.elf back into zipread.c, move zipread into proc get zipos execve working again --- libc/calls/isapemagic.c | 8 +++ libc/proc/execve.c | 2 +- libc/proc/execve.internal.h | 3 +- libc/proc/fexecve.c | 82 +++++++++++++++++++++++++---- test/libc/calls/BUILD.mk | 7 +-- test/libc/proc/BUILD.mk | 31 +++++++++-- test/libc/proc/execve_test.c | 43 ++++++++++++++- test/libc/proc/fexecve_test.c | 68 ++++++++++-------------- test/libc/{calls => proc}/zipread.c | 0 9 files changed, 182 insertions(+), 62 deletions(-) rename test/libc/{calls => proc}/zipread.c (100%) diff --git a/libc/calls/isapemagic.c b/libc/calls/isapemagic.c index fa4f8b75edc..bf0d80ba228 100644 --- a/libc/calls/isapemagic.c +++ b/libc/calls/isapemagic.c @@ -28,3 +28,11 @@ bool IsApeLoadable(char buf[8]) { READ64LE(buf) == READ64LE("jartsr='") || READ64LE(buf) == READ64LE("APEDBG='"); } + +/** + * Returns true if executable image is an APE + */ +bool IsApeMagic(char buf[8]) { + return READ64LE(buf) == READ64LE("MZqFpD='") || + READ64LE(buf) == READ64LE("JTqFpD='"); +} diff --git a/libc/proc/execve.c b/libc/proc/execve.c index 41c3fdc497f..a9666b89753 100644 --- a/libc/proc/execve.c +++ b/libc/proc/execve.c @@ -76,7 +76,7 @@ int execve(const char *prog, char *const argv[], char *const envp[]) { rc = _weaken(sys_pledge_linux)(__execpromises, __pledge_mode); } if (!rc) { - if (0 && _weaken(__zipos_parseuri) && + if (_weaken(__zipos_parseuri) && (_weaken(__zipos_parseuri)(prog, &uri) != -1)) { rc = _weaken(__zipos_open)(&uri, O_RDONLY | O_CLOEXEC); if (rc != -1) { diff --git a/libc/proc/execve.internal.h b/libc/proc/execve.internal.h index af8746fa62e..794cf20cfde 100644 --- a/libc/proc/execve.internal.h +++ b/libc/proc/execve.internal.h @@ -2,7 +2,8 @@ #define COSMOPOLITAN_LIBC_CALLS_EXECVE_SYSV_H_ COSMOPOLITAN_C_START_ -bool IsApeLoadable(char[8]) libcesque; +bool IsApeLoadable(char[8]); +bool IsApeMagic(char[8]); COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_CALLS_EXECVE_SYSV_H_ */ diff --git a/libc/proc/fexecve.c b/libc/proc/fexecve.c index 05202750864..06f1e42899e 100644 --- a/libc/proc/fexecve.c +++ b/libc/proc/fexecve.c @@ -34,6 +34,7 @@ #include "libc/intrin/strace.internal.h" #include "libc/intrin/weaken.h" #include "libc/limits.h" +#include "libc/paths.h" #include "libc/proc/execve.internal.h" #include "libc/str/str.h" #include "libc/sysv/consts/f.h" @@ -42,12 +43,14 @@ #include "libc/sysv/consts/mfd.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/prot.h" +#include "libc/sysv/consts/s.h" #include "libc/sysv/consts/shm.h" #include "libc/sysv/errfuns.h" +#include "libc/zip.internal.h" static bool IsAPEFd(const int fd) { char buf[8]; - return (sys_pread(fd, buf, 8, 0, 0) == 8) && IsApeLoadable(buf); + return (sys_pread(fd, buf, 8, 0, 0) == 8) && IsApeMagic(buf); } static int fexecve_impl(const int fd, char *const argv[], char *const envp[]) { @@ -64,6 +67,57 @@ static int fexecve_impl(const int fd, char *const argv[], char *const envp[]) { return rc; } +#define defer(fn) __attribute__((cleanup(fn))) + +void cleanup_close(int *pFD) { + STRACE("time to close"); + if (*pFD != -1) { + close(*pFD); + } +} +#define defer_close defer(cleanup_close) + +void cleanup_unlink(const char **path) { + STRACE("time to unlink"); + if (*path != NULL) { + sys_unlink(*path); + } +} +#define defer_unlink defer(cleanup_unlink) + +static bool ape_to_elf_execve(void *ape, const size_t apesize) { + if (!_weaken(fork) || !_weaken(exit) || !IsLinux()) { + return false; + } + defer_unlink const char *tempfile = "/dev/shm/ape_to_elf_execve_XXXXXX"; + defer_close int fd = open(tempfile, O_RDWR | O_CREAT | O_EXCL, S_IRWXU); + if (fd == -1) { + tempfile = NULL; + return false; + } + if ((sys_ftruncate(fd, apesize, apesize) == -1) || (write(fd, ape, apesize) != apesize)) { + return false; + } + close(fd); + fd = -1; + int child = _weaken(fork)(); + if (child == -1) { + return false; + } else if (child == 0) { + __sys_execve(_PATH_BSHELL, (char *const[]){_PATH_BSHELL, (char*)tempfile, "--assimilate", NULL}, (char *const[]){NULL}); + _weaken(exit)(1); + } + int wstatus; + if ((child != waitpid(child, &wstatus, 0)) || !WIFEXITED(wstatus) || (WEXITSTATUS(wstatus) != 0)) { + return false; + } + return ((fd = open(tempfile, O_RDWR, S_IRWXU)) != -1) && (pread(fd, ape, apesize, 0) == apesize); +} + +#undef defer_unlink +#undef defer_close +#undef defer + typedef enum { PTF_NUM = 1 << 0, PTF_NUM2 = 1 << 1, @@ -72,6 +126,7 @@ typedef enum { } PTF_PARSE; static bool ape_to_elf(void *ape, const size_t apesize) { + return ape_to_elf_execve(ape, apesize); static const char printftok[] = "printf '"; static const size_t printftoklen = sizeof(printftok) - 1; const char *tok = memmem(ape, apesize, printftok, printftoklen); @@ -142,15 +197,21 @@ 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) && 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; + if (success) { + int ziperror; + if ((st.st_size > 8) && IsApeMagic(space)) { + success = ape_to_elf(space, st.st_size); + } + // we need to preserve the fd over exec if there's zipos + if (success && _weaken(GetZipEocd) && _weaken(GetZipEocd)(space, st.st_size, &ziperror)) { + int flags = fcntl(fd, F_GETFD); + if ((success = (flags != -1) && + (fcntl(fd, F_SETFD, flags & (~FD_CLOEXEC)) != -1))) { + const int newfd = fcntl(fd, F_DUPFD, 9001); + if (newfd != -1) { + close(fd); + fd = newfd; + } } } } @@ -233,7 +294,6 @@ int fexecve(int fd, char *const argv[], char *const envp[]) { } 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; diff --git a/test/libc/calls/BUILD.mk b/test/libc/calls/BUILD.mk index 9f2e6ea9c98..a16e90e3ff4 100644 --- a/test/libc/calls/BUILD.mk +++ b/test/libc/calls/BUILD.mk @@ -19,9 +19,7 @@ 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/zipread.com.dbg \ - o/$(MODE)/test/libc/calls/zipread.com + o/$(MODE)/test/libc/calls/life-classic.com TEST_LIBC_CALLS_TESTS = \ $(TEST_LIBC_CALLS_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) @@ -118,8 +116,7 @@ o/$(MODE)/test/libc/calls/life-nomod.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 \ -o/$(MODE)/test/libc/calls/zipread.com.zip.o: private \ +o/$(MODE)/test/libc/calls/life-classic.com.zip.o: private \ ZIPOBJ_FLAGS += \ -B diff --git a/test/libc/proc/BUILD.mk b/test/libc/proc/BUILD.mk index fb3aaa887ca..c26cbf58fa8 100644 --- a/test/libc/proc/BUILD.mk +++ b/test/libc/proc/BUILD.mk @@ -14,7 +14,9 @@ TEST_LIBC_PROC_COMS = \ TEST_LIBC_PROC_BINS = \ $(TEST_LIBC_PROC_COMS) \ - $(TEST_LIBC_PROC_COMS:%=%.dbg) + $(TEST_LIBC_PROC_COMS:%=%.dbg) \ + o/$(MODE)/test/libc/proc/zipread.com.dbg \ + o/$(MODE)/test/libc/proc/zipread.com TEST_LIBC_PROC_TESTS = \ $(TEST_LIBC_PROC_SRCS_TEST:%.c=o/$(MODE)/%.com.ok) @@ -90,6 +92,7 @@ o/$(MODE)/test/libc/proc/execve_test.com.dbg: \ o/$(MODE)/test/libc/proc/execve_test.o \ o/$(MODE)/test/libc/calls/life-nomod.com.zip.o \ o/$(MODE)/test/libc/proc/execve_test_prog1.com.zip.o \ + o/$(MODE)/test/libc/proc/echo.elf.zip.o \ o/$(MODE)/test/libc/mem/prog/life.elf.zip.o \ o/$(MODE)/test/libc/mem/prog/sock.elf.zip.o \ o/$(MODE)/test/libc/proc/proc.pkg \ @@ -102,16 +105,38 @@ o/$(MODE)/test/libc/proc/fexecve_test.com.dbg: \ $(TEST_LIBC_PROC_DEPS) \ o/$(MODE)/test/libc/proc/fexecve_test.o \ o/$(MODE)/test/libc/proc/proc.pkg \ + o/$(MODE)/test/libc/proc/echo.elf.zip.o \ o/$(MODE)/test/libc/mem/prog/life.elf.zip.o \ o/$(MODE)/test/libc/calls/life-nomod.com.zip.o \ - o/$(MODE)/test/libc/calls/zipread.com.zip.o \ + o/$(MODE)/test/libc/proc/zipread.com.zip.o \ $(LIBC_TESTMAIN) \ $(CRT) \ $(APE_NO_MODIFY_SELF) @$(APELINK) +o/$(MODE)/test/libc/proc/zipread.com.dbg: \ + $(LIBC_RUNTIME) \ + o/$(MODE)/test/libc/proc/zipread.o \ + o/$(MODE)/test/libc/mem/prog/life.elf.zip.o \ + $(CRT) \ + $(APE) + @$(APELINK) + +o/$(MODE)/test/libc/proc/echo.elf: \ + o/$(MODE)/tool/build/assimilate.com \ + o/$(MODE)/tool/build/echo.com + @$(COMPILE) -wACP -T$@ \ + build/bootstrap/cp.com \ + o/$(MODE)/tool/build/echo.com \ + o/$(MODE)/test/libc/proc/echo.elf + @$(COMPILE) -wAASSIMILATE -T$@ \ + o/$(MODE)/tool/build/assimilate.com -bcef \ + o/$(MODE)/test/libc/proc/echo.elf + +o/$(MODE)/test/libc/proc/echo.elf.zip.o \ o/$(MODE)/test/libc/proc/execve_test_prog1.com.zip.o \ -o/$(MODE)/test/libc/proc/life-pe.com.zip.o: private \ +o/$(MODE)/test/libc/proc/life-pe.com.zip.o \ +o/$(MODE)/test/libc/proc/zipread.com.zip.o: private \ ZIPOBJ_FLAGS += \ -B diff --git a/test/libc/proc/execve_test.c b/test/libc/proc/execve_test.c index 1d7b10bf4ba..acc7e3b5927 100644 --- a/test/libc/proc/execve_test.c +++ b/test/libc/proc/execve_test.c @@ -18,6 +18,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/calls.h" #include "libc/calls/struct/rusage.h" +#include "libc/calls/struct/stat.h" #include "libc/calls/syscall_support-sysv.internal.h" #include "libc/dce.h" #include "libc/errno.h" @@ -26,6 +27,8 @@ #include "libc/intrin/kprintf.h" #include "libc/runtime/runtime.h" #include "libc/str/str.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/s.h" #include "libc/temp.h" #include "libc/testlib/ezbench.h" #include "libc/testlib/subprocess.h" @@ -35,6 +38,9 @@ __static_yoink("zipos"); #define N 16 +int fds[2]; +char buf[8]; +bool HasProcFSAndMemfd = false; void SetUpOnce(void) { testlib_enable_tmp_setup_teardown(); } @@ -48,6 +54,21 @@ void GenBuf(char buf[8], int x) { } } +__attribute__((__constructor__)) static void init(void) { + char buf[8]; + if (__argc == 4 && !strcmp(__argv[1], "-")) { + GenBuf(buf, atoi(__argv[2])); + ASSERT_STREQ(buf, __argv[3]); + exit(0); + } + // zipos execve requires /proc and memfd_create + // TODO check for memfd + if (IsLinux()) { + struct stat st; + HasProcFSAndMemfd = stat("/proc/self/fd", &st) == 0 && S_ISDIR(st.st_mode); + } +} + TEST(execve, testArgPassing) { int i; char ibuf[12], buf[8]; @@ -64,10 +85,28 @@ TEST(execve, testArgPassing) { } } +TEST(execve, elfIsUnreadable_mayBeExecuted) { + if (IsWindows() || IsXnu()) return; + testlib_extract("/zip/echo.elf", "echo", 0111); + ASSERT_SYS(0, 0, pipe2(fds, O_CLOEXEC)); + SPAWN(vfork); + ASSERT_SYS(0, 1, dup2(4, 1)); + ASSERT_SYS( + 0, 0, + execve("echo", (char *const[]){"echo", "hi", 0}, (char *const[]){0})); + exit(1); + EXITS(0); + bzero(buf, 8); + ASSERT_SYS(0, 0, close(4)); + ASSERT_SYS(0, 3, read(3, buf, 7)); + ASSERT_SYS(0, 0, close(3)); + ASSERT_STREQ("hi\n", buf); +} + TEST(execve, ziposELF) { - if (1) return; // TODO: rewrite if (IsFreebsd()) return; // TODO: fixme on freebsd if (IsLinux() && !__is_linux_2_6_23()) return; // TODO: fixme on old linux + if (IsLinux() && !HasProcFSAndMemfd) return; if (!IsLinux() && !IsFreebsd()) { EXPECT_SYS(ENOSYS, -1, execve("/zip/life.elf", (char *const[]){0}, (char *const[]){0})); @@ -80,9 +119,9 @@ TEST(execve, ziposELF) { } TEST(execve, ziposAPE) { - if (1) return; // TODO: rewrite if (IsFreebsd()) return; // TODO: fixme on freebsd if (IsLinux() && !__is_linux_2_6_23()) return; // TODO: fixme on old linux + if (IsLinux() && !HasProcFSAndMemfd) return; if (!IsLinux() && !IsFreebsd()) { EXPECT_EQ(-1, execve("/zip/life-nomod.com", (char *const[]){0}, (char *const[]){0})); diff --git a/test/libc/proc/fexecve_test.c b/test/libc/proc/fexecve_test.c index 5c733b93584..190de62a4f8 100644 --- a/test/libc/proc/fexecve_test.c +++ b/test/libc/proc/fexecve_test.c @@ -16,8 +16,8 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#if 0 // TODO(G4Vi): improve reliability of fexecve() implementation #include "libc/calls/calls.h" +#include "libc/calls/struct/stat.h" #include "libc/calls/syscall_support-sysv.internal.h" #include "libc/dce.h" #include "libc/errno.h" @@ -25,6 +25,7 @@ #include "libc/str/str.h" #include "libc/sysv/consts/mfd.h" #include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/s.h" #include "libc/testlib/subprocess.h" #include "libc/testlib/testlib.h" // clang-format off @@ -33,6 +34,7 @@ __static_yoink("zipos"); int fds[2]; char buf[8]; +uint8_t elf_buf[4096]; void SetUpOnce(void) { testlib_enable_tmp_setup_teardown(); @@ -41,29 +43,18 @@ void SetUpOnce(void) { void SetUp(void) { if (IsFreebsd()) exit(0); // TODO: fixme on freebsd if (IsLinux() && !__is_linux_2_6_23()) exit(0); // TODO: fixme on old linux -} - -TEST(execve, elfIsUnreadable_mayBeExecuted) { - if (IsWindows() || IsXnu()) return; - testlib_extract("/zip/echo", "echo", 0111); - ASSERT_SYS(0, 0, pipe2(fds, O_CLOEXEC)); - SPAWN(vfork); - ASSERT_SYS(0, 1, dup2(4, 1)); - ASSERT_SYS( - 0, 0, - execve("echo", (char *const[]){"echo", "hi", 0}, (char *const[]){0})); - notpossible; - EXITS(0); - bzero(buf, 8); - ASSERT_SYS(0, 0, close(4)); - ASSERT_SYS(0, 3, read(3, buf, 7)); - ASSERT_SYS(0, 0, close(3)); - ASSERT_STREQ("hi\n", buf); + // linux fexecve relies on execve from /proc + if (IsLinux()) { + struct stat st; + if (stat("/proc/self/fd", &st) != 0 || !S_ISDIR(st.st_mode)) { + exit(0); + } + } } TEST(fexecve, elfIsUnreadable_mayBeExecuted) { if (!IsLinux() && !IsFreebsd()) return; - testlib_extract("/zip/echo", "echo", 0111); + testlib_extract("/zip/echo.elf", "echo", 0111); ASSERT_SYS(0, 0, pipe2(fds, O_CLOEXEC)); SPAWN(vfork); ASSERT_SYS(0, 1, dup2(4, 1)); @@ -71,7 +62,7 @@ TEST(fexecve, elfIsUnreadable_mayBeExecuted) { if (IsFreebsd()) ASSERT_SYS(0, 1, lseek(5, 1, SEEK_SET)); ASSERT_SYS(0, 0, fexecve(5, (char *const[]){"echo", "hi", 0}, (char *const[]){0})); - notpossible; + exit(1); EXITS(0); bzero(buf, 8); ASSERT_SYS(0, 0, close(4)); @@ -81,23 +72,27 @@ TEST(fexecve, elfIsUnreadable_mayBeExecuted) { } TEST(fexecve, memfd_create) { - if (1) return; // TODO: fixme if (!IsLinux()) return; - SPAWN(vfork); -#define TINY_ELF_PROGRAM "\ -\177\105\114\106\002\001\001\000\000\000\000\000\000\000\000\000\ -\002\000\076\000\001\000\000\000\170\000\100\000\000\000\000\000\ -\100\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\ -\000\000\000\000\100\000\070\000\001\000\000\000\000\000\000\000\ -\001\000\000\000\005\000\000\000\000\000\000\000\000\000\000\000\ -\000\000\100\000\000\000\000\000\000\000\100\000\000\000\000\000\ -\200\000\000\000\000\000\000\000\200\000\000\000\000\000\000\000\ -\000\020\000\000\000\000\000\000\152\052\137\152\074\130\017\005" + int life_fd = open("/zip/life.elf", O_RDONLY); + ASSERT_NE(-1, life_fd); int fd = memfd_create("foo", MFD_CLOEXEC); - if (fd == -1 && errno == ENOSYS) _Exit(42); - write(fd, TINY_ELF_PROGRAM, sizeof(TINY_ELF_PROGRAM) - 1); + if(fd == -1) { + ASSERT_EQ(ENOSYS, errno); + return; + } + while(1) { + const ssize_t bytes_read = read(life_fd, elf_buf, sizeof(elf_buf)); + if (bytes_read <= 0) { + ASSERT_LE(0, bytes_read); + break; + } + ASSERT_EQ(bytes_read, write(fd, elf_buf, bytes_read)); + } + ASSERT_SYS(0, 0, close(life_fd)); + SPAWN(vfork); fexecve(fd, (char *const[]){0}, (char *const[]){0}); EXITS(42); + ASSERT_SYS(0, 0, close(fd)); } TEST(fexecve, APE) { @@ -141,16 +136,11 @@ TEST(fexecve, ziposAPE) { } TEST(fexecve, ziposAPEHasZipos) { - if (1) return; // TODO: fixme if (!IsLinux() && !IsFreebsd()) return; int fd = open("/zip/zipread.com", O_RDONLY); ASSERT_NE(-1, fd); 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); } - -#endif diff --git a/test/libc/calls/zipread.c b/test/libc/proc/zipread.c similarity index 100% rename from test/libc/calls/zipread.c rename to test/libc/proc/zipread.c From 2df75e902be909c178e9222f0b7de2b08d9b128f Mon Sep 17 00:00:00 2001 From: Gavin Hayes Date: Wed, 6 Dec 2023 03:02:35 -0500 Subject: [PATCH 2/4] simplify execve_test support checking --- test/libc/proc/execve_test.c | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/test/libc/proc/execve_test.c b/test/libc/proc/execve_test.c index acc7e3b5927..71df7a72623 100644 --- a/test/libc/proc/execve_test.c +++ b/test/libc/proc/execve_test.c @@ -40,7 +40,7 @@ __static_yoink("zipos"); int fds[2]; char buf[8]; -bool HasProcFSAndMemfd = false; +bool SupportsFexecve = false; void SetUpOnce(void) { testlib_enable_tmp_setup_teardown(); } @@ -54,18 +54,18 @@ void GenBuf(char buf[8], int x) { } } -__attribute__((__constructor__)) static void init(void) { +void SetUp(void) { char buf[8]; if (__argc == 4 && !strcmp(__argv[1], "-")) { GenBuf(buf, atoi(__argv[2])); ASSERT_STREQ(buf, __argv[3]); exit(0); } - // zipos execve requires /proc and memfd_create - // TODO check for memfd if (IsLinux()) { + if (!__is_linux_2_6_23()) return; + // TODO check for memfd struct stat st; - HasProcFSAndMemfd = stat("/proc/self/fd", &st) == 0 && S_ISDIR(st.st_mode); + SupportsFexecve = stat("/proc/self/fd", &st) == 0 && S_ISDIR(st.st_mode); } } @@ -104,10 +104,8 @@ TEST(execve, elfIsUnreadable_mayBeExecuted) { } TEST(execve, ziposELF) { - if (IsFreebsd()) return; // TODO: fixme on freebsd - if (IsLinux() && !__is_linux_2_6_23()) return; // TODO: fixme on old linux - if (IsLinux() && !HasProcFSAndMemfd) return; - if (!IsLinux() && !IsFreebsd()) { + if (IsWindows()) return; + if (!SupportsFexecve) { EXPECT_SYS(ENOSYS, -1, execve("/zip/life.elf", (char *const[]){0}, (char *const[]){0})); return; @@ -119,10 +117,7 @@ TEST(execve, ziposELF) { } TEST(execve, ziposAPE) { - if (IsFreebsd()) return; // TODO: fixme on freebsd - if (IsLinux() && !__is_linux_2_6_23()) return; // TODO: fixme on old linux - if (IsLinux() && !HasProcFSAndMemfd) return; - if (!IsLinux() && !IsFreebsd()) { + if (!SupportsFexecve) { EXPECT_EQ(-1, execve("/zip/life-nomod.com", (char *const[]){0}, (char *const[]){0})); return; From 64275e9cd7bc753d8e521a4d5a1407188c2672d0 Mon Sep 17 00:00:00 2001 From: Gavin Hayes Date: Fri, 19 Jan 2024 02:49:26 -0500 Subject: [PATCH 3/4] avoid assimilating --- libc/proc/fexecve.c | 117 +++++++++++++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 33 deletions(-) diff --git a/libc/proc/fexecve.c b/libc/proc/fexecve.c index 06f1e42899e..5c6f02692f1 100644 --- a/libc/proc/fexecve.c +++ b/libc/proc/fexecve.c @@ -21,6 +21,7 @@ #include "libc/calls/calls.h" #include "libc/calls/cp.internal.h" #include "libc/calls/internal.h" +#include "libc/calls/state.internal.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/calls/struct/stat.internal.h" #include "libc/calls/syscall-sysv.internal.h" @@ -199,9 +200,9 @@ static int fd_to_mem_fd(const int infd, char *path) { bool success = readRc != -1; if (success) { int ziperror; - if ((st.st_size > 8) && IsApeMagic(space)) { - success = ape_to_elf(space, st.st_size); - } + //if ((st.st_size > 8) && IsApeMagic(space)) { + // success = ape_to_elf(space, st.st_size); + //} // we need to preserve the fd over exec if there's zipos if (success && _weaken(GetZipEocd) && _weaken(GetZipEocd)(space, st.st_size, &ziperror)) { int flags = fcntl(fd, F_GETFD); @@ -232,6 +233,30 @@ static int fd_to_mem_fd(const int infd, char *path) { return -1; } +static int isZipFd(const int fd) { + if (!_weaken(mmap) || !_weaken(munmap) || !_weaken(GetZipEocd)) { + return enosys(); + } + if (__vforked) { + return 0; + } + struct stat st; + if (fstat(fd, &st) == -1) { + return -1; + } + void *space = _weaken(mmap)(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (space == MAP_FAILED) { + STRACE("map failed"); + return -1; + } + int ziperror; + int rc = _weaken(GetZipEocd)(space, st.st_size, &ziperror) != NULL; + if(_weaken(munmap)(space, st.st_size) == -1) { + return -1; + } + return rc; +} + /** * Executes binary executable at file descriptor. * @@ -260,55 +285,81 @@ int fexecve(int fd, char *const argv[], char *const envp[]) { rc = enosys(); break; } - if (!__isfdkind(fd, kFdZip)) { - bool memfdReq; + int newfd = fd; + if (__isfdkind(fd, kFdZip)) { BLOCK_SIGNALS; BLOCK_CANCELATION; strace_enabled(-1); - memfdReq = ((rc = fcntl(fd, F_GETFD)) != -1) && (rc & FD_CLOEXEC) && - IsAPEFd(fd); + newfd = fd_to_mem_fd(fd, NULL); strace_enabled(+1); ALLOW_CANCELATION; ALLOW_SIGNALS; - if (rc == -1) { + if (newfd == -1) { break; - } else if (!memfdReq) { - fexecve_impl(fd, argv, envp); - if (errno != ENOEXEC) { - break; - } - savedErr = ENOEXEC; } } - int newfd; - char *path = alloca(PATH_MAX); + int isZipFdRc; BLOCK_SIGNALS; BLOCK_CANCELATION; - strace_enabled(-1); - newfd = fd_to_mem_fd(fd, path); + //strace_enabled(-1); + isZipFdRc = isZipFd(newfd); strace_enabled(+1); ALLOW_CANCELATION; ALLOW_SIGNALS; - if (newfd == -1) { - break; - } - size_t numenvs; - for (numenvs = 0; envp[numenvs];) ++numenvs; - 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; + if (isZipFdRc == -1) { + //savedErr = EACCES; + //STRACE("EACCESS 1"); + //break; + isZipFdRc = 0; } + bool isAPE; BLOCK_SIGNALS; BLOCK_CANCELATION; - strace_enabled(-1); - close(newfd); - strace_enabled(+1); + isAPE = IsAPEFd(newfd); ALLOW_CANCELATION; ALLOW_SIGNALS; + if ((isZipFdRc == 1) || isAPE) { + int flags = fcntl(newfd, F_GETFD); + if ((flags == -1) || (fcntl(newfd, F_SETFD, flags & (~FD_CLOEXEC)) == -1)) { + savedErr = EACCES; + STRACE("EACCESS 2"); + break; + } + const int highfd = fcntl(newfd, F_DUPFD, 9001); + if (highfd != -1) { + close(newfd); + newfd = highfd; + } + } + if (isZipFdRc == 1) { + char *path = alloca(PATH_MAX); + FormatInt32(stpcpy(path, "COSMOPOLITAN_INIT_ZIPOS="), newfd); + size_t numenvs; + for (numenvs = 0; envp[numenvs];) ++numenvs; + static _Thread_local char *envs[500]; + memcpy(envs, envp, numenvs * sizeof(char *)); + envs[numenvs] = path; + envs[numenvs + 1] = NULL; + envp = envs; + } + if (isAPE) { + char path[14 + 12]; + FormatInt32(stpcpy(path, "/proc/self/fd/"), newfd); + rc = sys_execve(path, argv, envp); + savedErr = errno; + BLOCK_SIGNALS; + BLOCK_CANCELATION; + strace_enabled(-1); + if (newfd != fd) { + close(newfd); + } + strace_enabled(+1); + ALLOW_CANCELATION; + ALLOW_SIGNALS; + break; + } + fexecve_impl(newfd, argv, envp); + savedErr = errno; } while (0); if (savedErr) { errno = savedErr; From 24d1e0865cf54667ec0e2501f2450b1fafcd9c0f Mon Sep 17 00:00:00 2001 From: Gavin Hayes Date: Thu, 25 Jan 2024 01:58:31 -0500 Subject: [PATCH 4/4] fexecve: cleanup --- libc/calls/isapemagic.c | 2 +- libc/proc/execve.internal.h | 2 +- libc/proc/fexecve.c | 261 ++++++++++++++---------------------- 3 files changed, 103 insertions(+), 162 deletions(-) diff --git a/libc/calls/isapemagic.c b/libc/calls/isapemagic.c index bf0d80ba228..a5f17f52913 100644 --- a/libc/calls/isapemagic.c +++ b/libc/calls/isapemagic.c @@ -32,7 +32,7 @@ bool IsApeLoadable(char buf[8]) { /** * Returns true if executable image is an APE */ -bool IsApeMagic(char buf[8]) { +bool IsApeMagic(const char buf[8]) { return READ64LE(buf) == READ64LE("MZqFpD='") || READ64LE(buf) == READ64LE("JTqFpD='"); } diff --git a/libc/proc/execve.internal.h b/libc/proc/execve.internal.h index 794cf20cfde..eb7028f40a9 100644 --- a/libc/proc/execve.internal.h +++ b/libc/proc/execve.internal.h @@ -3,7 +3,7 @@ COSMOPOLITAN_C_START_ bool IsApeLoadable(char[8]); -bool IsApeMagic(char[8]); +bool IsApeMagic(const char[8]); COSMOPOLITAN_C_END_ #endif /* COSMOPOLITAN_LIBC_CALLS_EXECVE_SYSV_H_ */ diff --git a/libc/proc/fexecve.c b/libc/proc/fexecve.c index 5c6f02692f1..6516db3e53e 100644 --- a/libc/proc/fexecve.c +++ b/libc/proc/fexecve.c @@ -86,84 +86,56 @@ void cleanup_unlink(const char **path) { } #define defer_unlink defer(cleanup_unlink) -static bool ape_to_elf_execve(void *ape, const size_t apesize) { - if (!_weaken(fork) || !_weaken(exit) || !IsLinux()) { - return false; +#undef defer_unlink +#undef defer_close +#undef defer + +static inline int isZipFile(const void *data, size_t data_size) { + if (!_weaken(GetZipEocd)) { + return enosys(); } - defer_unlink const char *tempfile = "/dev/shm/ape_to_elf_execve_XXXXXX"; - defer_close int fd = open(tempfile, O_RDWR | O_CREAT | O_EXCL, S_IRWXU); - if (fd == -1) { - tempfile = NULL; - return false; + int ziperror; + return _weaken(GetZipEocd)(data, data_size, &ziperror) != NULL; +} + +static int isFdAZipFile(const int fd) { + if (!_weaken(mmap) || !_weaken(munmap) || !_weaken(GetZipEocd) || __vforked) { + return enosys(); } - if ((sys_ftruncate(fd, apesize, apesize) == -1) || (write(fd, ape, apesize) != apesize)) { - return false; + + struct stat st; + if (fstat(fd, &st) == -1) { + return -1; } - close(fd); - fd = -1; - int child = _weaken(fork)(); - if (child == -1) { - return false; - } else if (child == 0) { - __sys_execve(_PATH_BSHELL, (char *const[]){_PATH_BSHELL, (char*)tempfile, "--assimilate", NULL}, (char *const[]){NULL}); - _weaken(exit)(1); + void *space = _weaken(mmap)(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); + if (space == MAP_FAILED) { + return -1; } - int wstatus; - if ((child != waitpid(child, &wstatus, 0)) || !WIFEXITED(wstatus) || (WEXITSTATUS(wstatus) != 0)) { - return false; + int rc = isZipFile(space, st.st_size); + if(_weaken(munmap)(space, st.st_size) == -1) { + return -1; } - return ((fd = open(tempfile, O_RDWR, S_IRWXU)) != -1) && (pread(fd, ape, apesize, 0) == apesize); + return rc; } -#undef defer_unlink -#undef defer_close -#undef defer - typedef enum { - PTF_NUM = 1 << 0, - PTF_NUM2 = 1 << 1, - PTF_NUM3 = 1 << 2, - PTF_ANY = 1 << 3 -} PTF_PARSE; + FEXEF_ZIP = 1 << 0, + FEXEF_APE = 1 << 1 +} FEXEF; -static bool ape_to_elf(void *ape, const size_t apesize) { - return ape_to_elf_execve(ape, 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; - } - } - } +static inline int getFexeFlags(const void *data, size_t data_size) { + if (!_weaken(GetZipEocd)) { + return enosys(); + } + int rc = isZipFile(data, data_size); + if (rc == -1) { + return -1; + } + int flags = rc << 0; + if (data_size >= 8) { + flags |= (int)IsApeMagic(data) << 1; } - errno = ENOEXEC; - return false; + return flags; } /** @@ -171,7 +143,7 @@ static bool ape_to_elf(void *ape, const size_t apesize) { * * This does an inplace conversion of APE to ELF when detected!!!! */ -static int fd_to_mem_fd(const int infd, char *path) { +static int fd_to_mem_fd(const int infd, FEXEF *flags) { if ((!IsLinux() && !IsFreebsd()) || !_weaken(mmap) || !_weaken(munmap)) { return enosys(); } @@ -199,28 +171,12 @@ 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) { - int ziperror; - //if ((st.st_size > 8) && IsApeMagic(space)) { - // success = ape_to_elf(space, st.st_size); - //} - // we need to preserve the fd over exec if there's zipos - if (success && _weaken(GetZipEocd) && _weaken(GetZipEocd)(space, st.st_size, &ziperror)) { - int flags = fcntl(fd, F_GETFD); - if ((success = (flags != -1) && - (fcntl(fd, F_SETFD, flags & (~FD_CLOEXEC)) != -1))) { - const int newfd = fcntl(fd, F_DUPFD, 9001); - if (newfd != -1) { - close(fd); - fd = newfd; - } - } - } + int fexe_flags = getFexeFlags(space, st.st_size); + success = fexe_flags != -1; + *flags = fexe_flags; } 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) { @@ -233,37 +189,13 @@ static int fd_to_mem_fd(const int infd, char *path) { return -1; } -static int isZipFd(const int fd) { - if (!_weaken(mmap) || !_weaken(munmap) || !_weaken(GetZipEocd)) { - return enosys(); - } - if (__vforked) { - return 0; - } - struct stat st; - if (fstat(fd, &st) == -1) { - return -1; - } - void *space = _weaken(mmap)(0, st.st_size, PROT_READ, MAP_SHARED, fd, 0); - if (space == MAP_FAILED) { - STRACE("map failed"); - return -1; - } - int ziperror; - int rc = _weaken(GetZipEocd)(space, st.st_size, &ziperror) != NULL; - if(_weaken(munmap)(space, st.st_size) == -1) { - return -1; - } - return rc; -} - /** * 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. + * supported. Zipos is supported. Zipos fds are copied to a new memfd. Zip files + * and APE files are F_DUPFD to a high number with FD_CLOEXEC turned off. APE + * files are ran with execve. * * @param fd is opened executable and current file position is ignored * @return doesn't return on success, otherwise -1 w/ errno @@ -279,59 +211,71 @@ int fexecve(int fd, char *const argv[], char *const envp[]) { } else { STRACE("fexecve(%d, %s, %s) → ...", fd, DescribeStringList(argv), DescribeStringList(envp)); - int savedErr = 0; + int newfd = fd; do { if (!IsLinux() && !IsFreebsd()) { rc = enosys(); break; } - int newfd = fd; + FEXEF fflags; if (__isfdkind(fd, kFdZip)) { BLOCK_SIGNALS; BLOCK_CANCELATION; strace_enabled(-1); - newfd = fd_to_mem_fd(fd, NULL); + newfd = fd_to_mem_fd(fd, &fflags); strace_enabled(+1); ALLOW_CANCELATION; ALLOW_SIGNALS; if (newfd == -1) { break; } + } else { + if (!__vforked) { + int isFdAZipFileRc; + BLOCK_SIGNALS; + BLOCK_CANCELATION; + strace_enabled(-1); + isFdAZipFileRc = isFdAZipFile(newfd); + strace_enabled(+1); + ALLOW_CANCELATION; + ALLOW_SIGNALS; + if (isFdAZipFileRc == -1) { + break; + } + fflags = isFdAZipFileRc << 0; + } + bool isAPE; + BLOCK_SIGNALS; + BLOCK_CANCELATION; + isAPE = IsAPEFd(newfd); + ALLOW_CANCELATION; + ALLOW_SIGNALS; + fflags |= (int)isAPE << 1; } - int isZipFdRc; - BLOCK_SIGNALS; - BLOCK_CANCELATION; - //strace_enabled(-1); - isZipFdRc = isZipFd(newfd); - strace_enabled(+1); - ALLOW_CANCELATION; - ALLOW_SIGNALS; - if (isZipFdRc == -1) { - //savedErr = EACCES; - //STRACE("EACCESS 1"); - //break; - isZipFdRc = 0; - } - bool isAPE; - BLOCK_SIGNALS; - BLOCK_CANCELATION; - isAPE = IsAPEFd(newfd); - ALLOW_CANCELATION; - ALLOW_SIGNALS; - if ((isZipFdRc == 1) || isAPE) { - int flags = fcntl(newfd, F_GETFD); - if ((flags == -1) || (fcntl(newfd, F_SETFD, flags & (~FD_CLOEXEC)) == -1)) { - savedErr = EACCES; - STRACE("EACCESS 2"); + if (fflags) { + int flags; + BLOCK_SIGNALS; + BLOCK_CANCELATION; + flags = fcntl(newfd, F_GETFD); + if (flags != -1) { + flags = fcntl(newfd, F_SETFD, flags & (~FD_CLOEXEC)); + } + ALLOW_CANCELATION; + ALLOW_SIGNALS; + if (flags == -1) { break; } + BLOCK_SIGNALS; + BLOCK_CANCELATION; const int highfd = fcntl(newfd, F_DUPFD, 9001); if (highfd != -1) { close(newfd); newfd = highfd; } + ALLOW_CANCELATION; + ALLOW_SIGNALS; } - if (isZipFdRc == 1) { + if (fflags & FEXEF_ZIP) { char *path = alloca(PATH_MAX); FormatInt32(stpcpy(path, "COSMOPOLITAN_INIT_ZIPOS="), newfd); size_t numenvs; @@ -342,27 +286,24 @@ int fexecve(int fd, char *const argv[], char *const envp[]) { envs[numenvs + 1] = NULL; envp = envs; } - if (isAPE) { + if (fflags & FEXEF_APE) { char path[14 + 12]; - FormatInt32(stpcpy(path, "/proc/self/fd/"), newfd); - rc = sys_execve(path, argv, envp); - savedErr = errno; - BLOCK_SIGNALS; - BLOCK_CANCELATION; - strace_enabled(-1); - if (newfd != fd) { - close(newfd); - } - strace_enabled(+1); - ALLOW_CANCELATION; - ALLOW_SIGNALS; + FormatInt32(stpcpy(path, "/dev/fd/"), newfd); + sys_execve(path, argv, envp); break; } fexecve_impl(newfd, argv, envp); - savedErr = errno; } while (0); - if (savedErr) { - errno = savedErr; + if (newfd != fd) { + int keepErrno = errno; + BLOCK_SIGNALS; + BLOCK_CANCELATION; + strace_enabled(-1); + close(newfd); + strace_enabled(+1); + ALLOW_CANCELATION; + ALLOW_SIGNALS; + errno = keepErrno; } rc = -1; }