Add APE fexecve() support (#733)

This commit is contained in:
Gavin Hayes 2023-02-22 21:58:23 -05:00 committed by GitHub
parent b275e664ec
commit ff9c15f48a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 191 additions and 29 deletions

View file

@ -19,6 +19,7 @@
#include "libc/calls/blockcancel.internal.h" #include "libc/calls/blockcancel.internal.h"
#include "libc/calls/blocksigs.internal.h" #include "libc/calls/blocksigs.internal.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.h"
#include "libc/calls/execve-sysv.internal.h"
#include "libc/calls/syscall-sysv.internal.h" #include "libc/calls/syscall-sysv.internal.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h" #include "libc/errno.h"
@ -37,6 +38,11 @@ static bool CanExecute(const char *path) {
return !sys_faccessat(AT_FDCWD, path, X_OK, 0); 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) { static bool IsApeBinary(const char *path) {
int fd; int fd;
char buf[8]; char buf[8];
@ -44,11 +50,7 @@ static bool IsApeBinary(const char *path) {
// TODO(jart): Should we block signals too? // TODO(jart): Should we block signals too?
BLOCK_CANCELLATIONS; BLOCK_CANCELLATIONS;
if ((fd = sys_open(path, O_RDONLY, 0)) != -1) { if ((fd = sys_open(path, O_RDONLY, 0)) != -1) {
if (sys_read(fd, buf, 8) == 8 && // res = sys_read(fd, buf, 8) == 8 && IsAPEMagic(buf);
(READ64LE(buf) == READ64LE("MZqFpD='") ||
READ64LE(buf) == READ64LE("JTqFpD='"))) {
res = true;
}
sys_close(fd); sys_close(fd);
} }
ALLOW_CANCELLATIONS; ALLOW_CANCELLATIONS;

View file

@ -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_ */

View file

@ -16,21 +16,147 @@
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/assert.h"
#include "libc/calls/blockcancel.internal.h"
#include "libc/calls/blocksigs.internal.h"
#include "libc/calls/calls.h" #include "libc/calls/calls.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/calls/syscall-sysv.internal.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/errno.h"
#include "libc/fmt/itoa.h" #include "libc/fmt/itoa.h"
#include "libc/intrin/asan.internal.h" #include "libc/intrin/asan.internal.h"
#include "libc/intrin/describeflags.internal.h" #include "libc/intrin/describeflags.internal.h"
#include "libc/intrin/strace.internal.h" #include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
#include "libc/str/str.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" #include "libc/sysv/errfuns.h"
static bool IsAPEFd(const int fd) {
char buf[8];
bool res;
return (sys_pread(fd, buf, 8, 0, 0) == 8) && IsAPEMagic(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;
}
static int ape_fd_to_mem_elf_fd(const int infd, char *path) {
if (!IsLinux() && !IsFreebsd() || !_weaken(mmap) || !_weaken(munmap)) {
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;
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 = sys_pread(infd, space, st.st_size, 0, 0);
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. * Executes binary executable at file descriptor.
* *
* This is only supported on Linux and FreeBSD. APE binaries currently * This is only supported on Linux and FreeBSD. APE binaries are
* aren't supported. * supported.
* *
* @param fd is opened executable and current file position is ignored * @param fd is opened executable and current file position is ignored
* @return doesn't return on success, otherwise -1 w/ errno * @return doesn't return on success, otherwise -1 w/ errno
@ -39,7 +165,6 @@
*/ */
int fexecve(int fd, char *const argv[], char *const envp[]) { int fexecve(int fd, char *const argv[], char *const envp[]) {
int rc; int rc;
size_t i;
if (!argv || !envp || if (!argv || !envp ||
(IsAsan() && (IsAsan() &&
(!__asan_is_valid_strlist(argv) || !__asan_is_valid_strlist(envp)))) { (!__asan_is_valid_strlist(argv) || !__asan_is_valid_strlist(envp)))) {
@ -47,14 +172,21 @@ int fexecve(int fd, char *const argv[], char *const envp[]) {
} else { } else {
STRACE("fexecve(%d, %s, %s) → ...", fd, DescribeStringList(argv), STRACE("fexecve(%d, %s, %s) → ...", fd, DescribeStringList(argv),
DescribeStringList(envp)); DescribeStringList(envp));
if (IsLinux()) { rc = fexecve_impl(fd, argv, envp);
char path[14 + 12]; if ((errno == ENOEXEC) && (IsLinux() || IsFreebsd())) {
FormatInt32(stpcpy(path, "/proc/self/fd/"), fd); int newfd = -1;
rc = __sys_execve(path, argv, envp); BEGIN_CANCELLATION_POINT;
} else if (IsFreebsd()) { BLOCK_SIGNALS;
rc = sys_fexecve(fd, argv, envp); strace_enabled(-1);
} else { if (IsAPEFd(fd)) {
rc = enosys(); newfd = ape_fd_to_mem_elf_fd(fd, NULL);
}
strace_enabled(+1);
ALLOW_SIGNALS;
END_CANCELLATION_POINT;
if (newfd != -1) {
rc = fexecve_impl(newfd, argv, envp);
}
} }
} }
STRACE("fexecve(%d) failed %d% m", fd, rc); STRACE("fexecve(%d) failed %d% m", fd, rc);

View file

@ -105,6 +105,7 @@ i32 sys_setresuid(u32, u32, u32) _Hide;
i32 sys_setreuid(u32, u32) _Hide; i32 sys_setreuid(u32, u32) _Hide;
i32 sys_setsid(void) _Hide; i32 sys_setsid(void) _Hide;
i32 sys_setuid(i32) _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_sigaction(i32, const void *, void *, i64, i64) _Hide;
i32 sys_sigaltstack(const void *, void *) _Hide; i32 sys_sigaltstack(const void *, void *) _Hide;
i32 sys_symlinkat(const char *, i32, const char *) _Hide; i32 sys_symlinkat(const char *, i32, const char *) _Hide;

View file

@ -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_LOCKED 0x0400 0 0 0 0 0
syscon shm SHM_NORESERVE 0x1000 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_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 syscon lock LOCK_UNLOCK_CACHE 54 0 0 0 0 0 # wut

View file

@ -0,0 +1,2 @@
.include "o/libc/sysv/consts/syscon.internal.inc"
.syscon shm,SHM_ANON,0,0,1,0,0,0

View file

@ -2,24 +2,26 @@
#define COSMOPOLITAN_LIBC_SYSV_CONSTS_SHM_H_ #define COSMOPOLITAN_LIBC_SYSV_CONSTS_SHM_H_
#include "libc/runtime/symbolic.h" #include "libc/runtime/symbolic.h"
#define SHM_DEST SYMBOLIC(SHM_DEST) #define SHM_ANON SYMBOLIC(SHM_ANON)
#define SHM_EXEC SYMBOLIC(SHM_EXEC) #define SHM_DEST SYMBOLIC(SHM_DEST)
#define SHM_HUGETLB SYMBOLIC(SHM_HUGETLB) #define SHM_EXEC SYMBOLIC(SHM_EXEC)
#define SHM_INFO SYMBOLIC(SHM_INFO) #define SHM_HUGETLB SYMBOLIC(SHM_HUGETLB)
#define SHM_LOCK SYMBOLIC(SHM_LOCK) #define SHM_INFO SYMBOLIC(SHM_INFO)
#define SHM_LOCKED SYMBOLIC(SHM_LOCKED) #define SHM_LOCK SYMBOLIC(SHM_LOCK)
#define SHM_LOCKED SYMBOLIC(SHM_LOCKED)
#define SHM_NORESERVE SYMBOLIC(SHM_NORESERVE) #define SHM_NORESERVE SYMBOLIC(SHM_NORESERVE)
#define SHM_R SYMBOLIC(SHM_R) #define SHM_R SYMBOLIC(SHM_R)
#define SHM_RDONLY SYMBOLIC(SHM_RDONLY) #define SHM_RDONLY SYMBOLIC(SHM_RDONLY)
#define SHM_REMAP SYMBOLIC(SHM_REMAP) #define SHM_REMAP SYMBOLIC(SHM_REMAP)
#define SHM_RND SYMBOLIC(SHM_RND) #define SHM_RND SYMBOLIC(SHM_RND)
#define SHM_STAT SYMBOLIC(SHM_STAT) #define SHM_STAT SYMBOLIC(SHM_STAT)
#define SHM_UNLOCK SYMBOLIC(SHM_UNLOCK) #define SHM_UNLOCK SYMBOLIC(SHM_UNLOCK)
#define SHM_W SYMBOLIC(SHM_W) #define SHM_W SYMBOLIC(SHM_W)
#if !(__ASSEMBLER__ + __LINKER__ + 0) #if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
extern const char *SHM_ANON;
extern const int SHM_DEST; extern const int SHM_DEST;
extern const int SHM_EXEC; extern const int SHM_EXEC;
extern const int SHM_HUGETLB; extern const int SHM_HUGETLB;

View file

@ -86,3 +86,14 @@ TEST(fexecve, memfd_create) {
fexecve(fd, (char *const[]){0}, (char *const[]){0}); fexecve(fd, (char *const[]){0}, (char *const[]){0});
EXITS(42); 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);
}

View file

@ -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/fexecve_test.o \
o/$(MODE)/test/libc/calls/calls.pkg \ o/$(MODE)/test/libc/calls/calls.pkg \
o/$(MODE)/tool/build/echo.zip.o \ o/$(MODE)/tool/build/echo.zip.o \
o/$(MODE)/test/libc/calls/life-nomod.com.zip.o \
$(LIBC_TESTMAIN) \ $(LIBC_TESTMAIN) \
$(CRT) \ $(CRT) \
$(APE_NO_MODIFY_SELF) $(APE_NO_MODIFY_SELF)