diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e4415ab..673c381 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,6 @@ on: push: branches: - '*' - pull_request: jobs: check: @@ -15,7 +14,7 @@ jobs: - uses: nttld/setup-ndk@v1 id: setup-ndk with: - ndk-version: r26d + ndk-version: r28c link-to-sdk: true - run: brew install clang-format - run: make CC="${ANDROID_NDK_HOME}"/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang @@ -24,7 +23,13 @@ jobs: - run: make check CLANG_TIDY="${ANDROID_NDK_HOME}/toolchains/llvm/prebuilt/linux-x86_64/bin/clang-tidy --extra-arg=--target=aarch64-linux-android29" env: ANDROID_NDK_HOME: ${{ steps.setup-ndk.outputs.ndk-path }} + + host-build-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 - run: make unit-test CC=clang HOST_BUILD=1 + - run: make on-normal-linux-tests CC=clang HOST_BUILD=1 actionlint: runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index f85e6ed..90ee657 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ *.swp *.deb test-binary +tests/execl +tests/exec-directory tests/fexecve tests/popen tests/system-uname diff --git a/Makefile b/Makefile index 09d147b..82f61f8 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,10 @@ CC ?= clang TERMUX_BASE_DIR ?= /data/data/com.termux/files -CFLAGS += -Wall -Wextra -Werror -Wshadow -fvisibility=hidden -std=c17 +CFLAGS += -Wall -Wextra -Werror -Wshadow -fvisibility=hidden -std=c23 -D__USE_GNU C_SOURCE := src/termux-exec.c src/exec-variants.c src/termux-readlink.c CLANG_FORMAT := clang-format --sort-includes --style="{ColumnLimit: 120}" $(C_SOURCE) tests/fexecve.c tests/system-uname.c tests/print-argv0.c tests/popen.c CLANG_TIDY ?= clang-tidy +TEST_BINARIES = tests/execl tests/exec-directory tests/fexecve tests/popen tests/system-uname tests/readlink-proc-self-exe ifeq ($(SANITIZE),1) CFLAGS += -O1 -g -fsanitize=address -fno-omit-frame-pointer @@ -13,11 +14,19 @@ endif ifeq ($(HOST_BUILD),1) CFLAGS += -Wno-error=tautological-pointer-compare +else + TEST_BINARIES += $(TERMUX_BASE_DIR)/usr/bin/termux-exec-test-print-argv0 endif libtermux-exec.so: $(C_SOURCE) $(CC) $(CFLAGS) $(LDFLAGS) $(C_SOURCE) -DTERMUX_PREFIX=\"$(TERMUX_PREFIX)\" -DTERMUX_BASE_DIR=\"$(TERMUX_BASE_DIR)\" -shared -fPIC -o libtermux-exec.so +tests/execl: tests/execl.c + $(CC) $(CFLAGS) -DTERMUX_BASE_DIR=\"$(TERMUX_BASE_DIR)\" $< -o $@ + +tests/exec-directory: tests/exec-directory.c + $(CC) $(CFLAGS) -DTERMUX_BASE_DIR=\"$(TERMUX_BASE_DIR)\" $< -o $@ + tests/fexecve: tests/fexecve.c $(CC) $(CFLAGS) -DTERMUX_BASE_DIR=\"$(TERMUX_BASE_DIR)\" $< -o $@ @@ -34,7 +43,7 @@ $(TERMUX_BASE_DIR)/usr/bin/termux-exec-test-print-argv0: tests/print-argv0.c $(CC) $(CFLAGS) $< -o $@ clean: - rm -f libtermux-exec.so tests/*-actual test-binary $(TERMUX_BASE_DIR)/usr/bin/termux-exec-test-print-argv0 + rm -f libtermux-exec.so tests/*-actual $(TEST_BINARIES) install: libtermux-exec.so install libtermux-exec.so $(DESTDIR)$(PREFIX)/lib/libtermux-exec.so @@ -46,9 +55,12 @@ on-device-tests: make clean ASAN_OPTIONS=symbolize=0,detect_leaks=0 make on-device-tests-internal -on-device-tests-internal: libtermux-exec.so tests/fexecve tests/popen tests/system-uname tests/readlink-proc-self-exe $(TERMUX_BASE_DIR)/usr/bin/termux-exec-test-print-argv0 +on-device-tests-internal: libtermux-exec.so $(TEST_BINARIES) @LD_PRELOAD=${CURDIR}/libtermux-exec.so ./run-tests.sh +on-normal-linux-tests: $(TEST_BINARIES) + ./run-tests.sh + format: $(CLANG_FORMAT) -i $(C_SOURCE) tests/*.c diff --git a/run-tests.sh b/run-tests.sh index 0c831d7..9922f92 100755 --- a/run-tests.sh +++ b/run-tests.sh @@ -1,8 +1,18 @@ -#!/data/data/com.termux/files/usr/bin/bash +#!/bin/bash set -u +UNAME_OS=$(uname -o) +EXIT_CODE=0 + for f in tests/*.sh; do + if [ "$UNAME_OS" != Android ]; then + if [ "$f" = tests/call-system-bin-sh.sh ] || [ "$f" = tests/print-argv0.sh ] || [ "$f" = tests/fexecve.sh ] ; then + echo "Skipping $f..." + continue + fi + fi + printf "Running $f..." EXPECTED_FILE=$f-expected @@ -14,7 +24,14 @@ for f in tests/*.sh; do if cmp --silent $ACTUAL_FILE $EXPECTED_FILE; then printf " OK\n" else - printf " FAILED - compare expected $EXPECTED_FILE with ${ACTUAL_FILE}\n" + printf " FAILED - compare expected ${EXPECTED_FILE} with ${ACTUAL_FILE}\n" + echo "### Expected:" + cat "$EXPECTED_FILE" + echo "### Actual:" + cat "$ACTUAL_FILE" + echo "###" + EXIT_CODE=1 fi done +exit $EXIT_CODE diff --git a/src/termux-exec.c b/src/termux-exec.c index 0407cc8..68fb4d3 100644 --- a/src/termux-exec.c +++ b/src/termux-exec.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -284,9 +285,19 @@ __attribute__((visibility("default"))) int execve(const char *executable_path, c fprintf(stderr, LOG_PREFIX "Rewritten path: '%s'\n", executable_path); } + char normalized_path_buffer[PATH_MAX]; + executable_path = normalize_path(executable_path, normalized_path_buffer); + if (access(executable_path, X_OK) != 0) { - // Error out if the file is not executable: - errno = EACCES; + // Error out if the file is not executable. + struct stat stat_buffer; + if (stat(executable_path, &stat_buffer) == 0) { + // File exists but is executable: + errno = EACCES; + } else { + // File does not exist: + errno = ENOENT; + } return -1; } @@ -315,6 +326,13 @@ __attribute__((visibility("default"))) int execve(const char *executable_path, c // We use one more byte since inspect_file_header() will null terminate the buffer. char header[256]; ssize_t read_bytes = read(fd, header, sizeof(header) - 1); + if (read_bytes < 0) { + if (errno == EISDIR) { + // execve() should error with EACCES if file is directory. + errno = EACCES; + } + return -1; + } close(fd); struct file_header_info info = { @@ -338,9 +356,6 @@ __attribute__((visibility("default"))) int execve(const char *executable_path, c } } - char normalized_path_buffer[PATH_MAX]; - executable_path = normalize_path(executable_path, normalized_path_buffer); - char **new_allocated_envp = NULL; char *termux_self_exe = NULL; diff --git a/tests/exec-directory.c b/tests/exec-directory.c new file mode 100644 index 0000000..ae40bfd --- /dev/null +++ b/tests/exec-directory.c @@ -0,0 +1,11 @@ +#include +#include +#include + +int main(int, char **argv, char **env) { + execve(".", argv, env); + printf("errno = %d\n", errno); + execve("/", argv, env); + printf("errno = %d\n", errno); + return 0; +} diff --git a/tests/exec-directory.sh b/tests/exec-directory.sh new file mode 100755 index 0000000..496d7cb --- /dev/null +++ b/tests/exec-directory.sh @@ -0,0 +1,4 @@ +#!/bin/sh +set -e -u + +./tests/exec-directory diff --git a/tests/exec-directory.sh-expected b/tests/exec-directory.sh-expected new file mode 100644 index 0000000..664e139 --- /dev/null +++ b/tests/exec-directory.sh-expected @@ -0,0 +1,2 @@ +errno = 13 +errno = 13 diff --git a/tests/execl.c b/tests/execl.c new file mode 100644 index 0000000..5dfd790 --- /dev/null +++ b/tests/execl.c @@ -0,0 +1,22 @@ +#include +#include +#include + +void exec_debug(char *path) { + int ret = execl(path, "--arg", (char *)NULL); + if (ret != -1) { + fprintf(stderr, "Unexpected return value when execl():ing non-existing file: %d\n", ret); + } else { + printf("errno = %d\n", errno); + } +} + +int main(int argc, char **argv) { + if (argc != 2) { + fprintf(stderr, "Unexpected argc=%d\n", argc); + return 1; + } + exec_debug("/this-file-does-not-exist"); + exec_debug(argv[1]); + return 0; +} diff --git a/tests/execl.sh b/tests/execl.sh new file mode 100755 index 0000000..1c07105 --- /dev/null +++ b/tests/execl.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -e -u + +# Make TMPFILE an existing but non-executable file: +TMPFILE=$(mktemp) +echo "1" > $TMPFILE + +./tests/execl $TMPFILE + +rm $TMPFILE diff --git a/tests/execl.sh-expected b/tests/execl.sh-expected new file mode 100644 index 0000000..2ff95e4 --- /dev/null +++ b/tests/execl.sh-expected @@ -0,0 +1,2 @@ +errno = 2 +errno = 13 diff --git a/tests/fexecve.c b/tests/fexecve.c index bbc682c..e206591 100644 --- a/tests/fexecve.c +++ b/tests/fexecve.c @@ -1,5 +1,6 @@ #include #include +#define __USE_XOPEN2K8 #include int main() { diff --git a/tests/not-executable.sh-expected b/tests/not-executable.sh-expected index 5e7557b..d14d651 100644 --- a/tests/not-executable.sh-expected +++ b/tests/not-executable.sh-expected @@ -1 +1 @@ -./run-tests.sh: line 12: tests/not-executable.sh: Permission denied +./run-tests.sh: line 22: tests/not-executable.sh: Permission denied diff --git a/tests/popen.c b/tests/popen.c index a5cbf20..beb73ad 100644 --- a/tests/popen.c +++ b/tests/popen.c @@ -1,3 +1,4 @@ +#define _POSIX_C_SOURCE 2 #include int main() { diff --git a/tests/readlink-proc-self-exe.c b/tests/readlink-proc-self-exe.c index f041eb2..0d5e9c0 100644 --- a/tests/readlink-proc-self-exe.c +++ b/tests/readlink-proc-self-exe.c @@ -1,4 +1,6 @@ #define _DEFAULT_SOURCE +#define __USE_XOPEN2K8 +#include #include #include #include