From d8a85ec550576a0b7002f2749fd8953a812aa684 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Thu, 15 Feb 2018 10:13:47 +0100 Subject: [PATCH 01/47] Include for open(2) --- termux-exec.c | 1 + 1 file changed, 1 insertion(+) diff --git a/termux-exec.c b/termux-exec.c index 51fcb22..c37287a 100644 --- a/termux-exec.c +++ b/termux-exec.c @@ -1,4 +1,5 @@ #include +#include #include #include #include From a98452930be7f2bf4c3f2eaef026040adfd1e9ab Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Thu, 15 Feb 2018 10:14:05 +0100 Subject: [PATCH 02/47] Build with -Werror --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 416dc2e..36c54e8 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,7 @@ +CFLAGS += -Wall -Wextra -Werror -Oz + libtermux-exec.so: termux-exec.c - $(CC) $(CFLAGS) -Wall -Wextra -Oz termux-exec.c -shared -fPIC -o libtermux-exec.so + $(CC) $(CFLAGS) termux-exec.c -shared -fPIC -o libtermux-exec.so install: libtermux-exec.so install libtermux-exec.so $(PREFIX)/lib/libtermux-exec.so From d0ab1427f73d1d188121c32bed142cab00afb6a2 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Sun, 10 Mar 2019 22:40:50 +0100 Subject: [PATCH 03/47] Respect LDFLAGS in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 36c54e8..3d000fc 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CFLAGS += -Wall -Wextra -Werror -Oz libtermux-exec.so: termux-exec.c - $(CC) $(CFLAGS) termux-exec.c -shared -fPIC -o libtermux-exec.so + $(CC) $(CFLAGS) $(LDFLAGS) termux-exec.c -shared -fPIC -o libtermux-exec.so install: libtermux-exec.so install libtermux-exec.so $(PREFIX)/lib/libtermux-exec.so From 1720bb2513e312f02f97c4f9443069ace5782316 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Fri, 21 Feb 2020 23:13:59 +0100 Subject: [PATCH 04/47] Start android 10 proot wrapping experiment --- termux-exec.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/termux-exec.c b/termux-exec.c index c37287a..f8ae78a 100644 --- a/termux-exec.c +++ b/termux-exec.c @@ -1,6 +1,9 @@ #include +#include #include #include +#include +#include #include #include #include @@ -21,6 +24,16 @@ static const char* termux_rewrite_executable(const char* filename, char* buffer, int execve(const char* filename, char* const* argv, char *const envp[]) { + bool android_10_debug = getenv("TERMUX_ANDROID10_DEBUG") != NULL; + if (android_10_debug) { + printf("execve(%s):\n", filename); + int tmp_argv_count = 0; + while (argv[tmp_argv_count] != NULL) { + printf(" %s\n", argv[tmp_argv_count]); + tmp_argv_count++; + } + } + int fd = -1; const char** new_argv = NULL; @@ -92,6 +105,64 @@ int execve(const char* filename, char* const* argv, char *const envp[]) final: if (fd != -1) close(fd); int (*real_execve)(const char*, char *const[], char *const[]) = dlsym(RTLD_NEXT, "execve"); + + bool android_10_wrapping = getenv("TERMUX_ANDROID10") != NULL; + if (android_10_wrapping) { + char realpath_buffer[PATH_MAX]; + bool realpath_ok = realpath(filename, realpath_buffer) != NULL; + if (realpath_ok) { + bool wrap_in_proot = (strstr(realpath_buffer, "/data/data/com.termux/files") != NULL); + if (android_10_debug) { + printf("termux-exec: realpath(\"%s\") = \"%s\", wrapping=%s\n", filename, realpath_buffer, wrap_in_proot ? "yes" : "no"); + } + if (wrap_in_proot) { + orig_argv_count = 0; + while (argv[orig_argv_count] != NULL) orig_argv_count++; + + new_argv = malloc(sizeof(char*) * (2 + orig_argv_count)); + filename = "/data/data/com.termux/files/usr/bin/proot"; + new_argv[0] = "proot"; + for (int i = 0; i < orig_argv_count; i++) { + new_argv[i + 1] = argv[i]; + } + new_argv[orig_argv_count + 1] = NULL; + argv = (char**) new_argv; + + // Remove LD_PRELOAD environment variable when wrapping in proot: + for (int i = 0; envp[i] != NULL; i++) { + if (strstr(envp[i], "LD_PRELOAD=") == envp[i]) { + int env_length = 0; + while (envp[env_length] != NULL) env_length++; + + char** new_envp = malloc(sizeof(char*) * env_length); + int new_envp_idx = 0; + int old_envp_idx = 0; + while (old_envp_idx < env_length) { + if (old_envp_idx != i) { + new_envp[new_envp_idx++] = envp[old_envp_idx]; + } + old_envp_idx++; + } + new_envp[env_length] = NULL; + envp = new_envp; + break; + } + } + } + } else { + errno = 0; + } + + if (android_10_debug) { + printf("real_execve(%s):\n", filename); + int tmp_argv_count = 0; + while (argv[tmp_argv_count] != NULL) { + printf(" %s\n", argv[tmp_argv_count]); + tmp_argv_count++; + } + } + } + int ret = real_execve(filename, argv, envp); free(new_argv); return ret; From 7736a840641a37e3f525aa89d7c371fe6c4b5245 Mon Sep 17 00:00:00 2001 From: Leonid Pliushch Date: Fri, 30 Oct 2020 23:51:08 +0200 Subject: [PATCH 05/47] don't hardcode /data/data/com.termux/* --- termux-exec.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/termux-exec.c b/termux-exec.c index f8ae78a..144e5f6 100644 --- a/termux-exec.c +++ b/termux-exec.c @@ -9,14 +9,22 @@ #include #include +#ifndef TERMUX_BASE_DIR +# define TERMUX_BASE_DIR "/data/data/com.termux/files" +#endif + +#ifndef TERMUX_PREFIX +# define TERMUX_PREFIX "/data/data/com.termux/files/usr" +#endif + static const char* termux_rewrite_executable(const char* filename, char* buffer, int buffer_len) { - strcpy(buffer, "/data/data/com.termux/files/usr/bin/"); + strcpy(buffer, TERMUX_PREFIX "/bin/"); char* bin_match = strstr(filename, "/bin/"); if (bin_match == filename || bin_match == (filename + 4)) { // We have either found "/bin/" at the start of the string or at // "/xxx/bin/". Take the path after that. - strncpy(buffer + 36, bin_match + 5, buffer_len - 37); + strncpy(buffer + sizeof(TERMUX_PREFIX "/bin/"), bin_match + 5, buffer_len - sizeof(TERMUX_PREFIX "/bin/") - 1); filename = buffer; } return filename; @@ -111,7 +119,7 @@ int execve(const char* filename, char* const* argv, char *const envp[]) char realpath_buffer[PATH_MAX]; bool realpath_ok = realpath(filename, realpath_buffer) != NULL; if (realpath_ok) { - bool wrap_in_proot = (strstr(realpath_buffer, "/data/data/com.termux/files") != NULL); + bool wrap_in_proot = (strstr(realpath_buffer, TERMUX_BASE_DIR) != NULL); if (android_10_debug) { printf("termux-exec: realpath(\"%s\") = \"%s\", wrapping=%s\n", filename, realpath_buffer, wrap_in_proot ? "yes" : "no"); } @@ -120,7 +128,7 @@ int execve(const char* filename, char* const* argv, char *const envp[]) while (argv[orig_argv_count] != NULL) orig_argv_count++; new_argv = malloc(sizeof(char*) * (2 + orig_argv_count)); - filename = "/data/data/com.termux/files/usr/bin/proot"; + filename = TERMUX_PREFIX "/bin/proot"; new_argv[0] = "proot"; for (int i = 0; i < orig_argv_count; i++) { new_argv[i + 1] = argv[i]; From 4ca2778d350ac0dca21522d10571c15d5a777ec0 Mon Sep 17 00:00:00 2001 From: Leonid Pliushch Date: Fri, 30 Oct 2020 23:57:00 +0200 Subject: [PATCH 06/47] specify prefix & basedir from Makefile --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3d000fc..66a2008 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,9 @@ +TERMUX_PREFIX := /data/data/com.termux/files/usr +TERMUX_BASE_DIR := /data/data/com.termux/files CFLAGS += -Wall -Wextra -Werror -Oz libtermux-exec.so: termux-exec.c - $(CC) $(CFLAGS) $(LDFLAGS) termux-exec.c -shared -fPIC -o libtermux-exec.so + $(CC) $(CFLAGS) $(LDFLAGS) termux-exec.c -DTERMUX_PREFIX=\"$(TERMUX_PREFIX)\" -DTERMUX_BASE_DIR=\"$(TERMUX_BASE_DIR)\" -shared -fPIC -o libtermux-exec.so install: libtermux-exec.so install libtermux-exec.so $(PREFIX)/lib/libtermux-exec.so From 6b3499eaab572cb65cd1e124c8a65b25c284a8ff Mon Sep 17 00:00:00 2001 From: Leonid Pliushch Date: Sat, 31 Oct 2020 11:07:43 +0200 Subject: [PATCH 07/47] use the correct buffer offsets --- termux-exec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/termux-exec.c b/termux-exec.c index 144e5f6..90c229a 100644 --- a/termux-exec.c +++ b/termux-exec.c @@ -24,7 +24,7 @@ static const char* termux_rewrite_executable(const char* filename, char* buffer, if (bin_match == filename || bin_match == (filename + 4)) { // We have either found "/bin/" at the start of the string or at // "/xxx/bin/". Take the path after that. - strncpy(buffer + sizeof(TERMUX_PREFIX "/bin/"), bin_match + 5, buffer_len - sizeof(TERMUX_PREFIX "/bin/") - 1); + strncpy(buffer + sizeof(TERMUX_PREFIX "/bin/") - 1, bin_match + 5, buffer_len - sizeof(TERMUX_PREFIX "/bin/")); filename = buffer; } return filename; From 2e71dbd894c1c827fbe590fa911f37bae4370f30 Mon Sep 17 00:00:00 2001 From: "easyaspi314 (Devin)" Date: Wed, 3 Feb 2021 01:20:39 -0500 Subject: [PATCH 08/47] Unset LD_PRELOAD when ELF e_machine doesn't match. Fixes the long-standing "CANNOT LINK EXECUTABLE" bug when executing dynamic 32-bit programs on 64-bit. --- termux-exec.c | 82 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 55 insertions(+), 27 deletions(-) diff --git a/termux-exec.c b/termux-exec.c index 90c229a..3d48631 100644 --- a/termux-exec.c +++ b/termux-exec.c @@ -8,6 +8,7 @@ #include #include #include +#include #ifndef TERMUX_BASE_DIR # define TERMUX_BASE_DIR "/data/data/com.termux/files" @@ -17,6 +18,18 @@ # define TERMUX_PREFIX "/data/data/com.termux/files/usr" #endif +#ifdef __aarch64__ +# define EM_NATIVE EM_AARCH64 +#elif defined(__arm__) || defined(__thumb__) +# define EM_NATIVE EM_ARM +#elif defined(__x86_64__) +# define EM_NATIVE EM_X86_64 +#elif defined(__i386__) +# define EM_NATIVE EM_386 +#else +# error "unknown arch" +#endif + static const char* termux_rewrite_executable(const char* filename, char* buffer, int buffer_len) { strcpy(buffer, TERMUX_PREFIX "/bin/"); @@ -30,6 +43,29 @@ static const char* termux_rewrite_executable(const char* filename, char* buffer, return filename; } +static char*const * remove_ld_preload(char*const * envp) +{ + for (int i = 0; envp[i] != NULL; i++) { + if (strstr(envp[i], "LD_PRELOAD=") == envp[i]) { + int env_length = 0; + while (envp[env_length] != NULL) env_length++; + + char** new_envp = malloc(sizeof(char*) * env_length); + int new_envp_idx = 0; + int old_envp_idx = 0; + while (old_envp_idx < env_length) { + if (old_envp_idx != i) { + new_envp[new_envp_idx++] = envp[old_envp_idx]; + } + old_envp_idx++; + } + new_envp[env_length] = NULL; + return new_envp; + } + } + return envp; +} + int execve(const char* filename, char* const* argv, char *const envp[]) { bool android_10_debug = getenv("TERMUX_ANDROID10_DEBUG") != NULL; @@ -56,12 +92,23 @@ int execve(const char* filename, char* const* argv, char *const envp[]) // execve(2): "A maximum line length of 127 characters is allowed // for the first line in a #! executable shell script." - char shebang[128]; - ssize_t read_bytes = read(fd, shebang, sizeof(shebang) - 1); - if (read_bytes < 5 || !(shebang[0] == '#' && shebang[1] == '!')) goto final; + char header[128]; + ssize_t read_bytes = read(fd, header, sizeof(header) - 1); + + // If we are executing a non-native ELF file, unset LD_PRELOAD. + // This avoids CANNOT LINK EXECUTABLE errors when running 32-bit code + // on 64-bit. + if (read_bytes >= 20 && !memcmp(header, "\x7f\x45\x4c\x46", 4)) { + Elf32_Ehdr* ehdr = (Elf32_Ehdr*)header; + if (ehdr->e_machine != EM_NATIVE) { + envp = remove_ld_preload(envp); + } + goto final; + } + if (read_bytes < 5 || !(header[0] == '#' && header[1] == '!')) goto final; - shebang[read_bytes] = 0; - char* newline_location = strchr(shebang, '\n'); + header[read_bytes] = 0; + char* newline_location = strchr(header, '\n'); if (newline_location == NULL) goto final; // Strip whitespace at end of shebang: @@ -71,7 +118,7 @@ int execve(const char* filename, char* const* argv, char *const envp[]) *newline_location = 0; // Skip whitespace to find interpreter start: - char* interpreter = shebang + 2; + char* interpreter = header + 2; while (*interpreter == ' ') interpreter++; if (interpreter == newline_location) goto final; @@ -135,27 +182,8 @@ int execve(const char* filename, char* const* argv, char *const envp[]) } new_argv[orig_argv_count + 1] = NULL; argv = (char**) new_argv; - - // Remove LD_PRELOAD environment variable when wrapping in proot: - for (int i = 0; envp[i] != NULL; i++) { - if (strstr(envp[i], "LD_PRELOAD=") == envp[i]) { - int env_length = 0; - while (envp[env_length] != NULL) env_length++; - - char** new_envp = malloc(sizeof(char*) * env_length); - int new_envp_idx = 0; - int old_envp_idx = 0; - while (old_envp_idx < env_length) { - if (old_envp_idx != i) { - new_envp[new_envp_idx++] = envp[old_envp_idx]; - } - old_envp_idx++; - } - new_envp[env_length] = NULL; - envp = new_envp; - break; - } - } + // Remove LD_PRELOAD environment variable when wrapping in proot + envp = remove_ld_preload(envp); } } else { errno = 0; From 57acdd2a618098c9747e7a82cb70adad31e520d1 Mon Sep 17 00:00:00 2001 From: "easyaspi314 (Devin)" Date: Thu, 4 Feb 2021 11:48:04 -0500 Subject: [PATCH 09/47] Use ELFMAG macros. --- termux-exec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/termux-exec.c b/termux-exec.c index 3d48631..6172b33 100644 --- a/termux-exec.c +++ b/termux-exec.c @@ -98,7 +98,7 @@ int execve(const char* filename, char* const* argv, char *const envp[]) // If we are executing a non-native ELF file, unset LD_PRELOAD. // This avoids CANNOT LINK EXECUTABLE errors when running 32-bit code // on 64-bit. - if (read_bytes >= 20 && !memcmp(header, "\x7f\x45\x4c\x46", 4)) { + if (read_bytes >= 20 && !memcmp(header, ELFMAG, SELFMAG)) { Elf32_Ehdr* ehdr = (Elf32_Ehdr*)header; if (ehdr->e_machine != EM_NATIVE) { envp = remove_ld_preload(envp); From 138315521f8ccc75155cb5dd26ea5ef3f7abad75 Mon Sep 17 00:00:00 2001 From: "easyaspi314 (Devin)" Date: Sun, 26 Aug 2018 15:16:14 -0400 Subject: [PATCH 10/47] Unset LD_LIBRARY_PATH and LD_PRELOAD on /system/ executables. --- termux-exec.c | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/termux-exec.c b/termux-exec.c index 6172b33..1f8037d 100644 --- a/termux-exec.c +++ b/termux-exec.c @@ -30,8 +30,14 @@ # error "unknown arch" #endif +#define starts_with(value, str) !strncmp(value, str, sizeof(str) - 1) + static const char* termux_rewrite_executable(const char* filename, char* buffer, int buffer_len) { + if (starts_with(filename, TERMUX_BASE_DIR) || + starts_with(filename, "/system/")) + return filename; + strcpy(buffer, TERMUX_PREFIX "/bin/"); char* bin_match = strstr(filename, "/bin/"); if (bin_match == filename || bin_match == (filename + 4)) { @@ -66,7 +72,7 @@ static char*const * remove_ld_preload(char*const * envp) return envp; } -int execve(const char* filename, char* const* argv, char *const envp[]) +int execve(const char* filename, char* const* argv, char* const* envp) { bool android_10_debug = getenv("TERMUX_ANDROID10_DEBUG") != NULL; if (android_10_debug) { @@ -80,6 +86,7 @@ int execve(const char* filename, char* const* argv, char *const envp[]) int fd = -1; const char** new_argv = NULL; + const char** new_envp = NULL; char filename_buffer[512]; filename = termux_rewrite_executable(filename, filename_buffer, sizeof(filename_buffer)); @@ -90,6 +97,31 @@ int execve(const char* filename, char* const* argv, char *const envp[]) fd = open(filename, O_RDONLY); if (fd == -1) goto final; + // LD_LIBRARY_PATH messes up system programs with CANNOT_LINK_EXECUTABLE errors. + // If we remove.it, this problem is solved. + // /system/bin/sh is fine, it only uses libc++, libc, and libdl. + if (starts_with(filename, "/system/") && strcmp(filename, "/system/bin/sh") != 0) { + + size_t envp_count = 0; + while (envp[envp_count] != NULL) + envp_count++; + + new_envp = malloc((envp_count + 1) * sizeof(char*)); + + size_t pos = 0; + for (size_t i = 0; i < envp_count; i++) { + // Skip it if it is LD_LIBRARY_PATH or LD_PRELOAD + if (!starts_with(envp[i], "LD_LIBRARY_PATH=") && + !starts_with(envp[i], "LD_PRELOAD=")) + new_envp[pos++] = (const char*)envp[i]; + } + new_envp[pos] = NULL; + + envp = (char**)new_envp; + // Not.sure if needed. + environ = (char**)new_envp; + } + // execve(2): "A maximum line length of 127 characters is allowed // for the first line in a #! executable shell script." char header[128]; @@ -159,7 +191,7 @@ int execve(const char* filename, char* const* argv, char *const envp[]) final: if (fd != -1) close(fd); - int (*real_execve)(const char*, char *const[], char *const[]) = dlsym(RTLD_NEXT, "execve"); + int (*real_execve)(const char*, char* const[], char* const[]) = dlsym(RTLD_NEXT, "execve"); bool android_10_wrapping = getenv("TERMUX_ANDROID10") != NULL; if (android_10_wrapping) { @@ -201,5 +233,6 @@ int execve(const char* filename, char* const* argv, char *const envp[]) int ret = real_execve(filename, argv, envp); free(new_argv); + free(new_envp); return ret; } From b33392b5f0baef65b9b77bcbc69195b4ac8f15b1 Mon Sep 17 00:00:00 2001 From: Henrik Grimler Date: Sat, 15 May 2021 16:43:25 +0200 Subject: [PATCH 11/47] Makefile: respect DESTDIR --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 66a2008..dfe9d23 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ libtermux-exec.so: termux-exec.c $(CC) $(CFLAGS) $(LDFLAGS) termux-exec.c -DTERMUX_PREFIX=\"$(TERMUX_PREFIX)\" -DTERMUX_BASE_DIR=\"$(TERMUX_BASE_DIR)\" -shared -fPIC -o libtermux-exec.so install: libtermux-exec.so - install libtermux-exec.so $(PREFIX)/lib/libtermux-exec.so + install libtermux-exec.so $(DESTDIR)$(PREFIX)/lib/libtermux-exec.so uninstall: rm -f $(PREFIX)/lib/libtermux-exec.so From f3ae554e7c890b72c91bdc44ae2dfc8d141285a4 Mon Sep 17 00:00:00 2001 From: kt programs Date: Fri, 11 Feb 2022 11:33:04 +0800 Subject: [PATCH 12/47] Makefile: respect DESTDIR in uninstall as well --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index dfe9d23..fa5a3a0 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ install: libtermux-exec.so install libtermux-exec.so $(DESTDIR)$(PREFIX)/lib/libtermux-exec.so uninstall: - rm -f $(PREFIX)/lib/libtermux-exec.so + rm -f $(DESTDIR)$(PREFIX)/lib/libtermux-exec.so test: libtermux-exec.so @LD_PRELOAD=${CURDIR}/libtermux-exec.so ./run-tests.sh From cc873554182b3291d43e82863b6d734bf0c14c98 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 1 Mar 2025 02:00:15 +0500 Subject: [PATCH 13/47] Changed: Move `termux-exec.c` to `src/termux-exec.c` --- termux-exec.c => src/termux-exec.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename termux-exec.c => src/termux-exec.c (100%) diff --git a/termux-exec.c b/src/termux-exec.c similarity index 100% rename from termux-exec.c rename to src/termux-exec.c From 2fe477505c28efb96c61d1a750cc84fdff68c35d Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 1 Mar 2025 01:50:55 +0500 Subject: [PATCH 14/47] Added: Add support to execute ELF files by passing them to `/system/bin/linker*` to bypass android app data file exec restriction if using `targetSdkVersion` `>= 28` on Android `>= 10` - See the README file for a description for how `system_linker_exec` works. - If `TERMUX_EXEC_OPTOUT` environment variable is set, then do not intercept `execve()` at all to fix shebang issues or use `system_linker_exec`. - If `TERMUX_EXEC_DEBUG` environment variable is set, then log debug info for `termux-exec`. - Use `TERMUX__PREFIX` environment variable to extract termux rootfs directory path that's used to generate termux bin path to replace `/bin` and `/usr/bin` path in shebang of scripts instead of using hardcoded `TERMUX_BASE_DIR` build variable. - Since when executing with linker, the `/proc/self/exe` will be set to linker path, export `TERMUX_EXEC__PROC_SELF_EXE` environment variable with actual path to executable being executed so that packages can be patched to read it instead. - Added `src/exec-variants.c` to hook the entire `exec()` family of functions, which is required for Android 14. Closes termux/termux-packages#18537, termux/termux-app#3758 --- .clang-tidy | 2 + .gitignore | 4 ++ Makefile | 36 +++++++--- README.md | 75 +++++++++++++++++++- src/exec-variants.c | 154 +++++++++++++++++++++++++++++++++++++++++ termux-exec-debug.json | 17 +++++ test-program.c | 75 ++++++++++++++++++++ 7 files changed, 351 insertions(+), 12 deletions(-) create mode 100644 .clang-tidy create mode 100644 src/exec-variants.c create mode 100644 termux-exec-debug.json create mode 100644 test-program.c diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..42ff3fe --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,2 @@ +Checks: '-clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling,-clang-analyzer-security.insecureAPI.strcpy,-clang-analyzer-valist.Uninitialized' + diff --git a/.gitignore b/.gitignore index 1c90fce..794d717 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ *.so *.o *-actual +*.swo +*.swp +*.deb +test-binary diff --git a/Makefile b/Makefile index fa5a3a0..fbbbe8b 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,14 @@ -TERMUX_PREFIX := /data/data/com.termux/files/usr -TERMUX_BASE_DIR := /data/data/com.termux/files -CFLAGS += -Wall -Wextra -Werror -Oz +TERMUX_BASE_DIR ?= /data/data/com.termux/files +CFLAGS += -Wall -Wextra -Werror -Wshadow -O2 +C_SOURCE := src/termux-exec.c src/exec-variants.c +CLANG_FORMAT := clang-format --sort-includes --style="{ColumnLimit: 120}" $(C_SOURCE) +CLANG_TIDY ?= clang-tidy -libtermux-exec.so: termux-exec.c - $(CC) $(CFLAGS) $(LDFLAGS) termux-exec.c -DTERMUX_PREFIX=\"$(TERMUX_PREFIX)\" -DTERMUX_BASE_DIR=\"$(TERMUX_BASE_DIR)\" -shared -fPIC -o libtermux-exec.so +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 + +clean: + rm -f libtermux-exec.so tests/*-actual test-binary install: libtermux-exec.so install libtermux-exec.so $(DESTDIR)$(PREFIX)/lib/libtermux-exec.so @@ -11,10 +16,23 @@ install: libtermux-exec.so uninstall: rm -f $(DESTDIR)$(PREFIX)/lib/libtermux-exec.so -test: libtermux-exec.so +on-device-tests: libtermux-exec.so @LD_PRELOAD=${CURDIR}/libtermux-exec.so ./run-tests.sh -clean: - rm -f libtermux-exec.so tests/*-actual +format: + $(CLANG_FORMAT) -i $(C_SOURCE) + +check: + $(CLANG_FORMAT) --dry-run $(C_SOURCE) + $(CLANG_TIDY) -warnings-as-errors='*' $(C_SOURCE) -- -DTERMUX_BASE_DIR=\"$(TERMUX_BASE_DIR)\" + +test-binary: $(C_SOURCE) + $(CC) $(CFLAGS) $(LDFLAGS) $(C_SOURCE) -g -fsanitize=address -fno-omit-frame-pointer -DUNIT_TEST=1 -DTERMUX_BASE_DIR=\"$(TERMUX_BASE_DIR)\" -o test-binary + +deb: libtermux-exec.so + termux-create-package termux-exec-debug.json + +unit-test: test-binary + ./test-binary -.PHONY: clean install test uninstall +.PHONY: deb clean install uninstall test format check-format test diff --git a/README.md b/README.md index ae7caa9..aae5c9d 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,73 @@ # termux-exec -A `execve()` wrapper to fix problem with shebangs when running in Termux. +A `execve()` wrapper to fix two problems with exec-ing files in Termux. -# Problem +# Problem 1: Cannot execute files not part of the APK +Android 10 started blocking executing files under the app data directory, as +that is a [W^X](https://en.wikipedia.org/wiki/W%5EX) violation - files should be either +writeable or executable, but not both. Resources: + +- [Google Android issue](https://issuetracker.google.com/issues/128554619) +- [Termux: No more exec from data folder on targetAPI >= Android Q](https://github.com/termux/termux-app/issues/1072) +- [Termux: Revisit the Android W^X problem](https://github.com/termux/termux-app/issues/2155) + +While there is merit in that general principle, this prevents using Termux and Android +as a general computing device, where it should be possible for users to create executable +scripts and binaries. + +# Solution 1: Cannot execute files not part of the APK +Create an `exec` interceptor using [LD_PRELOAD](https://en.wikipedia.org/wiki/DLL_injection#Approaches_on_Unix-like_systems), +that instead of executing an ELF file directly, executes `/system/bin/linker64 /path/to/elf`. +Explanation follows below. + +On Linux, the kernel is normally responsible for loading both the executable and the +[dynamic linker](https://en.wikipedia.org/wiki/Dynamic_linker). The executable is invoked +by file path with the [execve system call](https://en.wikipedia.org/wiki/Exec_(system_call)). +The kernel loads the executable into the process, and looks for a `PT_INTERP` entry in +its [ELF program header table](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#Program_header) +of the file - this specifies the path to the dynamic linker (`/system/bin/linker64` for 64-bit Android). + +There is another way to load the two ELF objects: +[since 2018](https://android.googlesource.com/platform/bionic/+/8f639a40966c630c64166d2657da3ee641303194) +the dynamic linker can be invoked directly with `exec`. +If passed the filename of an executable, the dynamic linker will load and run the executable itself. +So, instead of executing `path/to/mybinary`, it's possible to execute +`/system/bin/linker64 /absolute/path/to/mybinary` (the linker needs an absolute path). + +This is what `termux-exec` does to circumvent the block on executing files in the data +directory - the kernel sees only `/system/bin/linker64` being executed. + +This also means that we need to extract [shebangs](https://en.wikipedia.org/wiki/Shebang_(Unix)). So for example, a call to execute: + +```sh +./path/to/myscript.sh +``` + +where the script has a `#!/path/to/interpreter` shebang, is replaced with: + +```sh +/system/bin/linker64 /path/to/interpreter ./path/to/myscript.sh +``` + +Implications: + +- It's important that `LD_PRELOAD` is kept - see e.g. [this change in sshd](https://github.com/termux/termux-packages/pull/18069). +We could also consider patching this exec interception into the build process of termux packages, so `LD_PRELOAD` would not be necessary for packages built by the termux-packages repository. + +- The executable will be `/system/bin/linker64`. So some programs that inspects the executable name (on itself or other programs) using `/proc/${PID}/exec` or `/proc/${PID}/comm` (where `$(PID}` could be `self`, for the current process) needs to be changed to instead inspect `argv0` of the process instead. See [this llvm driver change](https://github.com/termux/termux-packages/pull/18074) and [this pgrep/pkill change](https://github.com/termux/termux-packages/pull/18075). + +- Statically linked binaries will not work. These are rare in Android and Termux, but zig currently produces statically linked binaries against musl libc. + +- The interception using `LD_PRELOAD` will only work for programs using the [C library wrappers](https://linux.die.net/man/3/execve) for executing a new process, not when using the `execve` system call directly. Luckily most programs do use this. Programs using raw system calls needs to be patched or be run under [proot](https://wiki.termux.com/wiki/PRoot). + +**NOTE**: The above example used `/system/bin/linker64` - on 32-bit systems, the corresponding +path is `/system/bin/linker`. + +**NOTE**: While this circumvents the technical restriction, it still might be considered +violating [Google Play policy](https://support.google.com/googleplay/android-developer/answer/9888379). +So this workaround is not guaranteed to enable Play store distribution of Termux - but it's +worth an attempt, and regardless of Play store distribution, updating the targetSdk is necessary. + +# Problem 2: Shebang paths A lot of Linux software is written with the assumption that `/bin/sh`, `/usr/bin/env` and similar file exists. This is not the case on Android where neither `/bin/` nor `/usr/` exists. @@ -9,7 +75,7 @@ exists. When building packages for Termux those hard-coded assumptions are patched away - but this does not help with installing scripts and programs from other sources than Termux packages. -# Solution +# Solution 2: Shebang paths Create an `execve()` wrapper that rewrites calls to execute files under `/bin/` and `/usr/bin` into the matching Termux executables under `$PREFIX/bin/` and inject that into processes using `LD_PRELOAD`. @@ -22,3 +88,6 @@ using `LD_PRELOAD`. # Where is LD_PRELOAD set? The `$PREFIX/bin/login` program which is used to create new Termux sessions checks for `$PREFIX/lib/libtermux-exec.so` and if so sets up `LD_PRELOAD` before launching the login shell. + +Soon, when making a switch to target Android 10+, this will be setup by the Termux app even before +launching any process, as `LD_PRELOAD` will be necessary for anything non-system to execute. diff --git a/src/exec-variants.c b/src/exec-variants.c new file mode 100644 index 0000000..672793d --- /dev/null +++ b/src/exec-variants.c @@ -0,0 +1,154 @@ +// These exec variants, which ends up calling execve(), comes from bionic: +// https://android.googlesource.com/platform/bionic/+/refs/heads/main/libc/bionic/exec.cpp +// +// For some reason these are only necessary starting with Android 14 - before +// that intercepting execve() is enough. +// +// See the test-program.c for how to test the different variants. + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +enum { ExecL, ExecLE, ExecLP }; + +static int __exec_as_script(const char *buf, char *const *argv, char *const *envp) { + size_t arg_count = 1; + while (argv[arg_count] != NULL) + ++arg_count; + + const char *script_argv[arg_count + 2]; + script_argv[0] = "sh"; + script_argv[1] = buf; + memcpy(script_argv + 2, argv + 1, arg_count * sizeof(char *)); + return execve(_PATH_BSHELL, (char **const)script_argv, envp); +} + +int execv(const char *name, char *const *argv) { return execve(name, argv, environ); } + +int execvp(const char *name, char *const *argv) { return execvpe(name, argv, environ); } + +int execvpe(const char *name, char *const *argv, char *const *envp) { + // if (name == NULL || *name == '\0') { errno = ENOENT; return -1; } + + // If it's an absolute or relative path name, it's easy. + if (strchr(name, '/') && execve(name, argv, envp) == -1) { + if (errno == ENOEXEC) + return __exec_as_script(name, argv, envp); + return -1; + } + + // Get the path we're searching. + const char *path = getenv("PATH"); + if (path == NULL) + path = _PATH_DEFPATH; + + // Make a writable copy. + size_t len = strlen(path) + 1; + char writable_path[len]; + memcpy(writable_path, path, len); + + bool saw_EACCES = false; + + // Try each element of $PATH in turn... + char *strsep_buf = writable_path; + const char *dir; + while ((dir = strsep(&strsep_buf, ":"))) { + // It's a shell path: double, leading and trailing colons + // mean the current directory. + if (*dir == '\0') + dir = "."; + + size_t dir_len = strlen(dir); + size_t name_len = strlen(name); + + char buf[dir_len + 1 + name_len + 1]; + mempcpy(mempcpy(mempcpy(buf, dir, dir_len), "/", 1), name, name_len + 1); + + execve(buf, argv, envp); + switch (errno) { + case EISDIR: + case ELOOP: + case ENAMETOOLONG: + case ENOENT: + case ENOTDIR: + break; + case ENOEXEC: + return __exec_as_script(buf, argv, envp); + return -1; + case EACCES: + saw_EACCES = true; + break; + default: + return -1; + } + } + if (saw_EACCES) + errno = EACCES; + return -1; +} + +static int __execl(int variant, const char *name, const char *argv0, va_list ap) { + // Count the arguments. + va_list count_ap; + va_copy(count_ap, ap); + size_t n = 1; + while (va_arg(count_ap, char *) != NULL) { + ++n; + } + va_end(count_ap); + + // Construct the new argv. + char *argv[n + 1]; + argv[0] = (char *)argv0; + n = 1; + while ((argv[n] = va_arg(ap, char *)) != NULL) { + ++n; + } + + // Collect the argp too. + char **argp = (variant == ExecLE) ? va_arg(ap, char **) : environ; + + va_end(ap); + + return (variant == ExecLP) ? execvp(name, argv) : execve(name, argv, argp); +} + +int execl(const char *name, const char *arg, ...) { + va_list ap; + va_start(ap, arg); + int result = __execl(ExecL, name, arg, ap); + va_end(ap); + return result; +} + +int execle(const char *name, const char *arg, ...) { + va_list ap; + va_start(ap, arg); + int result = __execl(ExecLE, name, arg, ap); + va_end(ap); + return result; +} + +int execlp(const char *name, const char *arg, ...) { + va_list ap; + va_start(ap, arg); + int result = __execl(ExecLP, name, arg, ap); + va_end(ap); + return result; +} + +int fexecve(int fd, char *const *argv, char *const *envp) { + char buf[40]; + snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd); + execve(buf, argv, envp); + if (errno == ENOENT) + errno = EBADF; + return -1; +} diff --git a/termux-exec-debug.json b/termux-exec-debug.json new file mode 100644 index 0000000..4b45a7d --- /dev/null +++ b/termux-exec-debug.json @@ -0,0 +1,17 @@ +{ + "control": { + "Package": "termux-exec", + "Version": "9:9.8", + "Architecture": "aarch64", + "Maintainer": "Termux developers", + "Description": "Development version of termux-exec" + }, + + "installation_prefix": "/data/data/com.termux/files/usr", + + "data_files": { + "lib/libtermux-exec.so": { + "source": "libtermux-exec.so" + } + } +} diff --git a/test-program.c b/test-program.c new file mode 100644 index 0000000..a7f559f --- /dev/null +++ b/test-program.c @@ -0,0 +1,75 @@ +#include +#include +#include +#include + +#define TERMUX_APT_PATH "/data/data/com.termux/files/usr/bin/apt" + +extern char **environ; + +int main() { + if (fork() == 0) { + char *const argv[] = { "apt", "--version", NULL }; + printf("# execve\n"); + execve(TERMUX_APT_PATH, argv, environ); + return 0; + } + + sleep(1); + if (fork() == 0) { + printf("# execl\n"); + execl(TERMUX_APT_PATH, "apt", "--version", NULL); + return 0; + } + + sleep(1); + if (fork() == 0) { + printf("# execlp\n"); + execlp("apt", "apt", "--version", NULL); + return 0; + } + + sleep(1); + if (fork() == 0) { + printf("# execle\n"); + execle(TERMUX_APT_PATH, "apt", "--version", NULL, environ); + return 0; + } + + sleep(1); + if (fork() == 0) { + char *const argv[] = { "apt", "--version", NULL }; + printf("# execv\n"); + execv(TERMUX_APT_PATH, argv); + return 0; + } + + sleep(1); + if (fork() == 0) { + char *const argv[] = { "apt", "--version", NULL }; + printf("# execvp\n"); + execvp("apt", argv); + return 0; + } + + sleep(1); + if (fork() == 0) { + char *const argv[] = { "apt", "--version", NULL }; + printf("# execvpe\n"); + execvpe("apt", argv, environ); + return 0; + } + + sleep(1); + if (fork() == 0) { + char *const argv[] = { "apt", "--version", NULL }; + int fd = open(TERMUX_APT_PATH, 0); + printf("# fexecve\n"); + fexecve(fd, argv, environ); + return 0; + } + + sleep(1); + + return 0; +} From 7ccbc61ea4a33e68cbdb3d4464298228cf2f0fb8 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 1 Jan 2024 00:59:34 +0500 Subject: [PATCH 15/47] Changed: Move `termux-exec` Apache 2.0 license file content from `LICENSE` to `licenses/termux__termux-exec__Apache-2.0.md` file and use `debian/copyright` format in `LICENSE` file The `LICENSE` file will list other licenses in future. Also fix copyright statement. --- LICENSE | 205 +----------------- ...termux__termux-exec-package__Apache-2.0.md | 201 +++++++++++++++++ 2 files changed, 208 insertions(+), 198 deletions(-) create mode 100644 licenses/termux__termux-exec-package__Apache-2.0.md diff --git a/LICENSE b/LICENSE index 8dada3e..aadef44 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,10 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - 1. Definitions. - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "{}" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright {yyyy} {name of copyright owner} - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. +Files: + * +Comment: + The `termux-exec-package` repository is released under the `Apache-2.0` license, unless specified + differently in a file/directory or in any additional `Files` sections below. +License: [Apache-2.0](licenses/termux__termux-exec-package__Apache-2.0.md) diff --git a/licenses/termux__termux-exec-package__Apache-2.0.md b/licenses/termux__termux-exec-package__Apache-2.0.md new file mode 100644 index 0000000..f54acfd --- /dev/null +++ b/licenses/termux__termux-exec-package__Apache-2.0.md @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 termux + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From 1fac107314407c0b8e98d767c3710e7d7f6ae6fc Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 22 Mar 2025 01:31:39 +0500 Subject: [PATCH 16/47] Added|Changed|Fixed|Removed: Refactor `termux-exec` including for changes in 2fe47750 and add `libtermux-exec_nos_c_tre` and `libtermux-exec-direct-ld-preload.so` `$LD_PRELOAD` library variant - The `libtermux-exec_nos_c_tre` c library for the Android native operating system (`nos`) running in Termux runtime environment (`tre`) has been added to handle all the `LD_PRELOAD` intercepts implementation, and other functionality required for `termux-exec` like the environment/config. This can be used statically or dynamically for `termux-exec` executables/libraries and also for other packages. - The `libtermux-exec-direct-ld-preload.so` `$LD_PRELOAD` library variant has been added. It statically depends on `libtermux-exec_nos_c_tre` library and primarily includes `app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c`, which now exclusively only defines the functions intercepted by `libtermux-exec-direct-ld-preload.so` and directs them to their intercepts in respective source files of `libtermux-exec_nos_c_tre` library. The `libtermux-exec-direct-ld-preload.so` is used as primary `$LD_PRELOAD` library variant by copying it to `$TERMUX__PREFIX/usr/lib/libtermux-exec-ld-preload.so` and this path is exported in `$LD_PRELOAD` by the `login` script. For backward compatibility, `libtermux-exec.so` is also created as a copy of `libtermux-exec-ld-preload.so` so that older clients do not break which have exported path to `libtermux-exec.so` in `$LD_PRELOAD`. - The intercepts implementation of `execve` done by `src/termux-exec.c` has been moved to `lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c`. The `src/exec-variants.c` has been moved to `lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.c` which handles intercepts of the entire `exec()` family of functions except `execve()`. The intercept implementations are now called from `app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c`. - The `__attribute__((visibility("default")))` needs to be set for all the intercepted functions in `app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c` as `libtermux-exec-direct-ld-preload.so` is compiled with `-fvisibility=hidden`, so that no other function in the library other than ones explicitly labelled are exported. This can be confirmed by running `nm --demangle --dynamic --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/lib/libtermux-exec-direct-ld-preload.so` after building the package. - Revert order of functions in `src/exec/ExecVariantsIntercept.c` back to the one in AOSP and uncomment the `NULL` path checks for `execvpe()` and add its original license to file header. Log entries are also added to each variant to know which variant was originally intercepted. - The `system_linker_exec` support added in 2fe47750 and tests in `src/termux-exec.c` have been removed, and will be added again in later commits. - Removed the singular `TERMUX_EXEC_OPTOUT` environment variable. Opt outs should be confined to specific intercepts and logics. - Added the `string` environment variable `TERMUX_EXEC__EXECVE_CALL__INTERCEPT` for whether `execve` would be intercepted for shebang fix or `system_linker_exec` (to be added later). If set to `enable`, then `execve()` intercept will be enabled. If set to `disable`, then `execve()` intercept will be disabled. The default value is `enable`. The other wrapper functions in the `exec()` family of functions declared in `unistd.h` are always intercepted to solve some other issues on older Android versions, check [`libc/bionic/exec.cpp`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/bionic/exec.cpp) git history. - Use `normalizePath()` and `absolutizePath()` path functions from `libtermux-core_nos_c_tre` library for executable and interpreter path processing that has been tested on hundreds of test cases to handle all the required cases. - Fix the order for normalize, absolutize and replacing termux bin prefix for both executable path and interpreter, check comment in `execveIntercept()`. - Fix relative paths for interpreter path by absolutizing it. Previously, only prefix was being replaced. - Fix `argv[0]` for executing shell scripts where it should be set to the original interpreter set in the file as is instead of the `argv[0]` to `execve()` being intercepted for the executable. - Increase buffer size for executable file shebang header from `256` to `340` defined by `TERMUX__FILE_HEADER__BUFFER_SIZE` as per termux path limits, check comment in `ExecIntercept.h` file and [Termux File Path Limits](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits) docs. - Decrease max length of valid `TERMUX__ROOTFS` from `200` to `86` defined by `TERMUX__ROOTFS_DIR___MAX_LEN`, check `TermuxFile.h` file and [Termux File Path Limits](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits) docs. - Use `termuxPrefixPath()` functions from `libtermux-core_nos_c_tre` library, which also has buffer overflow checks and return errors for it. - Fix replacing prefix if termux rootfs is `/` or `/system` and executable equals the bin directory itself instead of a subfile. - Fix `fexecve()` where executable path would be `/proc/self/fd/` and checking if its under Termux app data directory directory would give wrong results. The `termuxApp_dataDir_isPathUnder()` function from `libtermux-core_nos_c_tre` library handles this by getting real path of file and ensuring that the real path is for the same file for which fd was open by comparing `stat.st_dev` and `stat.st_ino`. - Fix checking if executable is under Termux directories, like Termux app data directory (previously rootfs/base). Previously, `strstr(executable_path, termux_base_dir)` was used, which would check first occurrence of the substring `termux_base_dir` in `executable_path`, and not whether `executable_path` is under `termux_base_dir`. This is now properly handled by `isPathInDirPath()` via `termuxApp_dataDir_isPathUnder()` from `libtermux-core_nos_c_tre` library. - Do not use `TERMUX__PREFIX` to get Termux rootfs directory by getting its parent directory and use `TERMUX__ROOTFS` environment variable directly. There may also be cases where they `TERMUX__PREFIX` equals `TERMUX__ROOTFS`, and getting parent directory would result in wrong results. Now `termux_prefixDir_get()` from `libtermux-core_nos_c_tre` library is used, in which if environment variable is not set or is invalid as per `TERMUX__ROOTFS_DIR___MAX_LEN`, then it returns the default Termux prefix for which package was compiled for as long as its executable and readable to ensure `termux-exec` was not compiled for a different package. - The `modifyExecArgs()` function now handles all changes to the arguments. - Fix issues where `errno` may already be set when `execve` is entered, check comment in `init()` function of `TermuxExecProcess.c` where it is set to `0`. - Fix hardcoded `com.termux` values being used, all constants are replaced during building including the root scope of environment variables that are read and as per `TERMUX_ENV__S_ROOT` defined in `properties.sh` of `termux-pacakges` as `TERMUX_`. - Use `stringStartsWith()` from `libtermux-core_nos_c_tre` library, instead of `starts_with()` giving wrong results for `NULL` and empty strings. - Added logger framework from `libtermux-core_nos_c_tre` library with multiple log levels with log entries for all the important variable states to track logic. The singular `TERMUX_EXEC_DEBUG` environment variable has been removed. The `int` `TERMUX_EXEC__LOG_LEVEL` environment variable controls the log level based on `(OFF=0, NORMAL=1, DEBUG=2, VERBOSE=3, VVERBOSE=4 and VVVERBOSE=5)`. The default value is `1`. Normally, `termux-exec` does not log anything at default log level `1` (`NORMAL`) for intercepts even and will require setting log level to `>= 2` (`DEBUG`) to see log messages. To enable `VVERBOSE` logging for a command, you can run something like `TERMUX_EXEC__LOG_LEVEL=4 id -u`. - Added `TERMUX_EXEC_PKG__VERSION` `Makefile` parameter that gets logged on intercept for `termux-exec` package version currently installed. - Create or move all build files under the `build/output/` directory. It's better to build a directory structure for the files to be added to prefix under the `build/output/usr` directory than have a mix of using files under both `src/` and `build/` during installation. This way `src/` directory also doesn't get modified and will not contain the files with replaced constants that were created from `*.in` files. This also makes it easier to clean and build the deb since all built files exist under the same directory, which `termux-create-package` can also use with `source_recurse`. - The recursive replacement of termux constants is done with `find -exec sed` with `TERMUX__CONSTANTS__SED_ARGS` passed to it. Using `foreach` with the `call` function for `replace-termux-constants` does not work for some reason if multiple files exists, because somehow function definition itself like `@sed` gets passed to `sed` command as argument. So `replace-termux-constants` is only called for individual files. The sed replacements surround arguments with double quotes instead of single quotes, so it could potentially break on more cases during shell expansion, although regex variables weren't being escaped before either. - The `termux-exec-package.json` will now have correct version and be consistent with `build.sh`, and also include tests files, which wasn't being done before. - The `termux-exec-package.json` previously had hardcoded `aarch64` as architecture, now we find and replace it for the compiler based on which predefined architecture macro is defined. --- Makefile | 395 ++++++++++- .../TermuxExecDirectLDPreloadEntryPoint.c | 96 +++ .../v1/TermuxExecLibraryConfig.h | 24 + .../ld_preload/direct/exec/ExecIntercept.h | 278 ++++++++ .../direct/exec/ExecVariantsIntercept.h | 82 +++ .../process/termux_exec/TermuxExecProcess.h | 23 + .../termux_exec/TermuxExecShellEnvironment.h | 84 +++ .../src/TermuxExecLibraryConfig.c | 15 + .../ld_preload/direct/exec/ExecIntercept.c | 613 ++++++++++++++++++ .../direct/exec/ExecVariantsIntercept.c | 185 ++++++ .../process/termux_exec/TermuxExecProcess.c | 73 +++ .../termux_exec/TermuxExecShellEnvironment.c | 32 + packaging/debian/termux-exec-package.json.in | 20 + src/exec-variants.c | 154 ----- src/termux-exec.c | 238 ------- termux-exec-debug.json | 17 - 16 files changed, 1896 insertions(+), 433 deletions(-) create mode 100644 app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c create mode 100644 lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/TermuxExecLibraryConfig.h create mode 100644 lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h create mode 100644 lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.h create mode 100644 lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/os/process/termux_exec/TermuxExecProcess.h create mode 100644 lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h create mode 100644 lib/termux-exec_nos_c_tre/src/TermuxExecLibraryConfig.c create mode 100644 lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c create mode 100644 lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.c create mode 100644 lib/termux-exec_nos_c_tre/src/termux/os/process/termux_exec/TermuxExecProcess.c create mode 100644 lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c create mode 100644 packaging/debian/termux-exec-package.json.in delete mode 100644 src/exec-variants.c delete mode 100644 src/termux-exec.c delete mode 100644 termux-exec-debug.json diff --git a/Makefile b/Makefile index fbbbe8b..aee3d95 100644 --- a/Makefile +++ b/Makefile @@ -1,38 +1,385 @@ -TERMUX_BASE_DIR ?= /data/data/com.termux/files -CFLAGS += -Wall -Wextra -Werror -Wshadow -O2 -C_SOURCE := src/termux-exec.c src/exec-variants.c -CLANG_FORMAT := clang-format --sort-includes --style="{ColumnLimit: 120}" $(C_SOURCE) +export TERMUX_EXEC_PKG__VERSION := 1.0 +export TERMUX_EXEC_PKG__ARCH +export TERMUX_EXEC_PKG__INSTALL_PREFIX +export TERMUX_EXEC_PKG__TESTS__API_LEVEL := + +export TERMUX__NAME := Termux# Default value: `Termux` +export TERMUX__LNAME := termux# Default value: `termux` + +export TERMUX_APP__NAME := Termux# Default value: `Termux` +export TERMUX_APP__PACKAGE_NAME := com.termux# Default value: `com.termux` +export TERMUX_APP__DATA_DIR := /data/data/$(TERMUX_APP__PACKAGE_NAME)# Default value: `/data/data/com.termux` + +export TERMUX__ROOTFS := $(TERMUX_APP__DATA_DIR)/files# Default value: `/data/data/com.termux/files` +export TERMUX__PREFIX := $(TERMUX__ROOTFS)/usr# Default value: `/data/data/com.termux/files/usr` +export TERMUX__PREFIX__BIN_DIR := $(TERMUX__PREFIX)/bin# Default value: `/data/data/com.termux/files/usr/bin` +export TERMUX__PREFIX__INCLUDE_DIR := $(TERMUX__PREFIX)/include# Default value: `/data/data/com.termux/files/usr/include` +export TERMUX__PREFIX__LIB_DIR := $(TERMUX__PREFIX)/lib# Default value: `/data/data/com.termux/files/usr/lib` + +export TERMUX_ENV__S_ROOT := TERMUX_# Default value: `TERMUX_` +export TERMUX_ENV__SS_TERMUX := _# Default value: `_` +export TERMUX_ENV__S_TERMUX := $(TERMUX_ENV__S_ROOT)$(TERMUX_ENV__SS_TERMUX)# Default value: `TERMUX__` +export TERMUX_ENV__SS_TERMUX_APP := APP__# Default value: `APP__` +export TERMUX_ENV__S_TERMUX_APP := $(TERMUX_ENV__S_ROOT)$(TERMUX_ENV__SS_TERMUX_APP)# Default value: `TERMUX_APP__` +export TERMUX_ENV__SS_TERMUX_ROOTFS := ROOTFS__# Default value: `ROOTFS__` +export TERMUX_ENV__S_TERMUX_ROOTFS := $(TERMUX_ENV__S_ROOT)$(TERMUX_ENV__SS_TERMUX_ROOTFS)# Default value: `TERMUX_ROOTFS__` +export TERMUX_ENV__SS_TERMUX_EXEC := EXEC__# Default value: `EXEC__` +export TERMUX_ENV__S_TERMUX_EXEC := $(TERMUX_ENV__S_ROOT)$(TERMUX_ENV__SS_TERMUX_EXEC)# Default value: `TERMUX_EXEC__` +export TERMUX_ENV__SS_TERMUX_EXEC__TESTS := EXEC__TESTS__# Default value: `EXEC__TESTS__` +export TERMUX_ENV__S_TERMUX_EXEC__TESTS := $(TERMUX_ENV__S_ROOT)$(TERMUX_ENV__SS_TERMUX_EXEC__TESTS)# Default value: `TERMUX_EXEC__TESTS__` + + +# If architecture not set, find it for the compiler based on which +# predefined architecture macro is defined. The `shell` function +# replaces newlines with a space and a literal space cannot be entered +# in a makefile as its used as a splitter, hence $(SPACE) variable is +# created and used for matching. +ifeq ($(TERMUX_EXEC_PKG__ARCH),) + export override PREDEFINED_MACROS := $(shell $(CC) -x c /dev/null -dM -E) + override EMPTY := + override SPACE := $(EMPTY) $(EMPTY) + ifneq (,$(findstring $(SPACE)#define __i686__ 1$(SPACE),$(SPACE)$(PREDEFINED_MACROS)$(SPACE))) + override TERMUX_EXEC_PKG__ARCH := i686 + else ifneq (,$(findstring $(SPACE)#define __x86_64__ 1$(SPACE),$(SPACE)$(PREDEFINED_MACROS)$(SPACE))) + override TERMUX_EXEC_PKG__ARCH := x86_64 + else ifneq (,$(findstring $(SPACE)#define __aarch64__ 1$(SPACE),$(SPACE)$(PREDEFINED_MACROS)$(SPACE))) + override TERMUX_EXEC_PKG__ARCH := aarch64 + else ifneq (,$(findstring $(SPACE)#define __arm__ 1$(SPACE),$(SPACE)$(PREDEFINED_MACROS)$(SPACE))) + override TERMUX_EXEC_PKG__ARCH := arm + else + $(error Unsupported package arch) + endif +endif + + +export override IS_ON_DEVICE_BUILD := $(shell test -f "/system/bin/app_process" && echo 1 || echo 0) + + + +export override BUILD_DIR := build# Default value: `build` + +export override BUILD_OUTPUT_DIR := $(BUILD_DIR)/output# Default value: `build/output` + +export override TMP_BUILD_OUTPUT_DIR := $(BUILD_OUTPUT_DIR)/tmp# Default value: `build/output/tmp` + +export override PREFIX_BUILD_OUTPUT_DIR := $(BUILD_OUTPUT_DIR)/usr# Default value: `build/output/usr` +export override LIB_BUILD_OUTPUT_DIR := $(PREFIX_BUILD_OUTPUT_DIR)/lib# Default value: `build/output/usr/lib` +export override LIBEXEC_BUILD_OUTPUT_DIR := $(PREFIX_BUILD_OUTPUT_DIR)/libexec# Default value: `build/output/usr/libexec` +export override TESTS_BUILD_OUTPUT_DIR := $(LIBEXEC_BUILD_OUTPUT_DIR)/installed-tests/termux-exec# Default value: `build/output/usr/libexec/installed-tests/termux-exec` + +export override PACKAGING_BUILD_OUTPUT_DIR := $(BUILD_OUTPUT_DIR)/packaging# Default value: `build/output/packaging` +export override DEBIAN_PACKAGING_BUILD_OUTPUT_DIR := $(PACKAGING_BUILD_OUTPUT_DIR)/debian# Default value: `build/output/packaging/debian` + + + +export override BUILD_INSTALL_DIR := $(BUILD_DIR)/install# Default value: `build/install` +export override PREFIX_BUILD_INSTALL_DIR := $(BUILD_INSTALL_DIR)/usr# Default value: `build/install/usr` + +ifeq ($(TERMUX_EXEC_PKG__INSTALL_PREFIX),) + ifeq ($(DESTDIR)$(PREFIX),) + override TERMUX_EXEC_PKG__INSTALL_PREFIX := $(TERMUX__PREFIX) + else + override TERMUX_EXEC_PKG__INSTALL_PREFIX := $(DESTDIR)$(PREFIX) + endif +endif +export TERMUX_EXEC_PKG__INSTALL_PREFIX + + + +export override TERMUX__CONSTANTS__MACRO_FLAGS := \ + -DTERMUX_EXEC_PKG__VERSION=\"$(TERMUX_EXEC_PKG__VERSION)\" \ + -DTERMUX__NAME=\"$(TERMUX__NAME)\" \ + -DTERMUX__LNAME=\"$(TERMUX__LNAME)\" \ + -DTERMUX_APP__DATA_DIR=\"$(TERMUX_APP__DATA_DIR)\" \ + -DTERMUX__ROOTFS=\"$(TERMUX__ROOTFS)\" \ + -DTERMUX__PREFIX=\"$(TERMUX__PREFIX)\" \ + -DTERMUX__PREFIX__BIN_DIR=\"$(TERMUX__PREFIX__BIN_DIR)\" \ + -DTERMUX_ENV__S_TERMUX=\"$(TERMUX_ENV__S_TERMUX)\" \ + -DTERMUX_ENV__S_TERMUX_APP=\"$(TERMUX_ENV__S_TERMUX_APP)\" \ + -DTERMUX_ENV__S_TERMUX_ROOTFS=\"$(TERMUX_ENV__S_TERMUX_ROOTFS)\" \ + -DTERMUX_ENV__S_TERMUX_EXEC=\"$(TERMUX_ENV__S_TERMUX_EXEC)\" \ + -DTERMUX_ENV__S_TERMUX_EXEC__TESTS=\"$(TERMUX_ENV__S_TERMUX_EXEC__TESTS)\" + +export override TERMUX__CONSTANTS__SED_ARGS := \ + -e "s%[@]TERMUX_EXEC_PKG__VERSION[@]%$(TERMUX_EXEC_PKG__VERSION)%g" \ + -e "s%[@]TERMUX_EXEC_PKG__ARCH[@]%$(TERMUX_EXEC_PKG__ARCH)%g" \ + -e "s%[@]TERMUX__LNAME[@]%$(TERMUX__LNAME)%g" \ + -e "s%[@]TERMUX_APP__NAME[@]%$(TERMUX_APP__NAME)%g" \ + -e "s%[@]TERMUX_APP__PACKAGE_NAME[@]%$(TERMUX_APP__PACKAGE_NAME)%g" \ + -e "s%[@]TERMUX_APP__DATA_DIR[@]%$(TERMUX_APP__DATA_DIR)%g" \ + -e "s%[@]TERMUX__ROOTFS[@]%$(TERMUX__ROOTFS)%g" \ + -e "s%[@]TERMUX__PREFIX[@]%$(TERMUX__PREFIX)%g" \ + -e "s%[@]TERMUX_ENV__S_TERMUX[@]%$(TERMUX_ENV__S_TERMUX)%g" \ + -e "s%[@]TERMUX_ENV__S_TERMUX_APP[@]%$(TERMUX_ENV__S_TERMUX_APP)%g" \ + -e "s%[@]TERMUX_ENV__S_TERMUX_EXEC[@]%$(TERMUX_ENV__S_TERMUX_EXEC)%g" \ + -e "s%[@]TERMUX_ENV__S_TERMUX_EXEC__TESTS[@]%$(TERMUX_ENV__S_TERMUX_EXEC__TESTS)%g" + +define replace-termux-constants + sed $(TERMUX__CONSTANTS__SED_ARGS) "$1.in" > "$2/$$(basename "$1")" +endef + + + +export override CFLAGS_DEFAULT := +export override CPPFLAGS_DEFAULT := +export override LDFLAGS_DEFAULT := + +# If building with make directly without termux-pacakges build infrastructure, +# then allow custom path for `libtermux-core_*.a` as they may not be +# installed in `TERMUX__PREFIX__LIB_DIR`. +ifeq ($(LDFLAGS),) + ifneq ($(TERMUX_CORE_PKG__INSTALL_PREFIX),) + ifneq ($(shell test -d "$(TERMUX_CORE_PKG__INSTALL_PREFIX)" && echo 1 || echo 0), 1) + $(error The termux-core package install prefix directory does not exist at TERMUX_CORE_PKG__INSTALL_PREFIX '$(TERMUX_CORE_PKG__INSTALL_PREFIX)' path) + endif + override CPPFLAGS_DEFAULT += -I$(TERMUX_CORE_PKG__INSTALL_PREFIX)/include/termux-core + override LDFLAGS_DEFAULT += -L$(TERMUX_CORE_PKG__INSTALL_PREFIX)/lib + endif +endif + +override CPPFLAGS_DEFAULT += -isystem$(TERMUX__PREFIX__INCLUDE_DIR) +override LDFLAGS_DEFAULT += -L$(TERMUX__PREFIX__LIB_DIR) + +ifeq ($(TERMUX_EXEC_PKG__ARCH),arm) + # "We recommend using the -mthumb compiler flag to force the generation of 16-bit Thumb-2 instructions". + # - https://developer.android.com/ndk/guides/standalone_toolchain.html#abi_compatibility + override CFLAGS_DEFAULT += -march=armv7-a -mfpu=neon -mfloat-abi=softfp -mthumb + override LDFLAGS_DEFAULT += -march=armv7-a +else ifeq ($(TERMUX_EXEC_PKG__ARCH),i686) + # From $NDK/docs/CPU-ARCH-ABIS.html: + override CFLAGS_DEFAULT += -march=i686 -msse3 -mstackrealign -mfpmath=sse + # i686 seem to explicitly require '-fPIC'. + # - https://github.com/termux/termux-packages/issues/7215#issuecomment-906154438 + override CFLAGS_DEFAULT += -fPIC +endif + +# - https://github.com/termux/termux-packages/commit/b997c4ea +ifeq ($(IS_ON_DEVICE_BUILD), 0) + override LDFLAGS_DEFAULT += -Wl,-rpath=$(TERMUX__PREFIX__LIB_DIR) +endif + +# Android 7 started to support DT_RUNPATH (but not DT_RPATH). +override LDFLAGS_DEFAULT += -Wl,--enable-new-dtags + +# Avoid linking extra (unneeded) libraries. +override LDFLAGS_DEFAULT += -Wl,--as-needed + +# Basic hardening. +override LDFLAGS_DEFAULT += -Wl,-z,relro,-z,now + + +# Set default flags if building with make directly without termux-pacakges build infrastructure. +CFLAGS ?= $(CFLAGS_DEFAULT) +CXXFLAGS ?= $(CFLAGS_DEFAULT) +CPPFLAGS ?= $(CPPFLAGS_DEFAULT) +LDFLAGS ?= $(LDFLAGS_DEFAULT) + +# Force optimize for speed and do basic hardening. +export override CFLAGS_FORCE := -Wall -Wextra -Werror -Wshadow -O2 -D_FORTIFY_SOURCE=2 -fstack-protector-strong + +CFLAGS += $(CFLAGS_FORCE) +CXXFLAGS += $(CFLAGS_FORCE) + +FSANTIZE_FLAGS += -fsanitize=address -fsanitize-recover=address -fno-omit-frame-pointer + + + +override LIBTERMUX_EXEC__NOS__C__SOURCE_FILES := \ + lib/termux-exec_nos_c_tre/src/TermuxExecLibraryConfig.c \ + lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c \ + lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c \ + lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.c \ + lib/termux-exec_nos_c_tre/src/termux/os/process/termux_exec/TermuxExecProcess.c \ + lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c + +override LIBTERMUX_EXEC__NOS__C__OBJECT_FILES := $(patsubst lib/%.c,$(TMP_BUILD_OUTPUT_DIR)/lib/%.o,$(LIBTERMUX_EXEC__NOS__C__SOURCE_FILES)) + +override LIBTERMUX_EXEC__NOS__C__CPPFLAGS := \ + $(CPPFLAGS) -I "$(TERMUX__PREFIX)/include/termux-core" -I "lib/termux-exec_nos_c_tre/include" + +override LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR := $(TESTS_BUILD_OUTPUT_DIR)/lib/termux-exec_nos_c_tre + + + +override TERMUX_EXEC__MAIN_APP__TESTS_BUILD_OUTPUT_DIR := $(TESTS_BUILD_OUTPUT_DIR)/app/main + + + +# The `-L` flag must come before `$LDFLAGS`, otherwise old library +# installed in system library directory from previous builds +# will get used instead of newly built one in `$LIB_BUILD_OUTPUT_DIR`. +# The `-fvisibility=hidden` flag is passed so that no internal +# functions are exported. All exported functions must explicitly enable +# `default` visibility with `__attribute__((visibility("default")))`, +# like for the `main()` function. +# The `-Wl,--exclude-libs=ALL` flag is passed so that symbols from +# the `libtermux-core_nos_c_tre.a` static library linked are not exported. +# Run `nm --demangle --defined-only --extern-only ` to +# find exported symbols. +override TERMUX_EXEC_EXECUTABLE__C__BUILD_COMMAND := \ + $(CC) $(CFLAGS) $(LIBTERMUX_EXEC__NOS__C__CPPFLAGS) \ + -L$(LIB_BUILD_OUTPUT_DIR) $(LDFLAGS) -Wl,--exclude-libs=ALL \ + $(TERMUX__CONSTANTS__MACRO_FLAGS) \ + -fPIE -pie -fvisibility=hidden + +# The `-l` flags must be passed after object files for proper linking. +# The order of libraries matters too and any dependencies of a library +# must come after it. +override TERMUX_EXEC_EXECUTABLE__C__POST_LDFLAGS := -l:libtermux-exec_nos_c_tre.a -l:libtermux-core_nos_c_tre.a + + +CLANG_FORMAT := clang-format --sort-includes --style="{ColumnLimit: 120}" CLANG_TIDY ?= clang-tidy -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 + + +# - https://www.gnu.org/software/make/manual/html_node/Parallel-Disable.html +.NOTPARALLEL: + +all: | pre-build build-libtermux-exec_nos_c_tre build-libtermux-exec-direct-ld-preload build-termux-exec-main-app + @printf "\ntermux-exec-package: %s\n" "Building packaging/debian/*" + @mkdir -p $(DEBIAN_PACKAGING_BUILD_OUTPUT_DIR) + find packaging/debian -mindepth 1 -maxdepth 1 -type f -name "*.in" -exec sh -c \ + 'sed $(TERMUX__CONSTANTS__SED_ARGS) "$$1" > $(DEBIAN_PACKAGING_BUILD_OUTPUT_DIR)/"$$(basename "$$1" | sed "s/\.in$$//")"' sh "{}" \; + find $(DEBIAN_PACKAGING_BUILD_OUTPUT_DIR) -mindepth 1 -maxdepth 1 -type f \ + -regextype posix-extended -regex "^.*/(postinst|postrm|preinst|prerm)$$" \ + -exec chmod 700 {} \; + find $(DEBIAN_PACKAGING_BUILD_OUTPUT_DIR) -mindepth 1 -maxdepth 1 -type f \ + -regextype posix-extended -regex "^.*/(config|conffiles|templates|triggers|clilibs|fortran_mod|runit|shlibs|starlibs|symbols)$$" \ + -exec chmod 600 {} \; + + + @printf "\ntermux-exec-package: %s\n\n" "Build termux-exec-package successful" + +pre-build: | clean + @printf "termux-exec-package: %s\n" "Building termux-exec-package" + @mkdir -p $(BUILD_OUTPUT_DIR) + @mkdir -p $(TMP_BUILD_OUTPUT_DIR) + +build-termux-exec-main-app: + @printf "\ntermux-exec-package: %s\n" "Building app/main" + +build-libtermux-exec_nos_c_tre: + @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre" + @mkdir -p $(LIB_BUILD_OUTPUT_DIR) + + @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/lib/*.o" + for source_file in $(LIBTERMUX_EXEC__NOS__C__SOURCE_FILES); do \ + mkdir -p "$$(dirname "$(TMP_BUILD_OUTPUT_DIR)/$$source_file")" || exit $$?; \ + $(CC) -c $(CFLAGS) $(LIBTERMUX_EXEC__NOS__C__CPPFLAGS) \ + $(TERMUX__CONSTANTS__MACRO_FLAGS) \ + -fPIC -fvisibility=default \ + -o $(TMP_BUILD_OUTPUT_DIR)/"$$(echo "$$source_file" | sed -E "s/(.*)\.c$$/\1.o/")" \ + "$$source_file" || exit $$?; \ + done + + @# `nm --demangle --dynamic --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/lib/libtermux-exec_nos_c_tre.so` + @printf "\ntermux-exec-package: %s\n" "Building lib/libtermux-exec_nos_c_tre.so" + $(CC) $(CFLAGS) $(LIBTERMUX_EXEC__NOS__C__CPPFLAGS) \ + -L$(LIB_BUILD_OUTPUT_DIR) $(LDFLAGS) -Wl,--exclude-libs=ALL \ + $(TERMUX__CONSTANTS__MACRO_FLAGS) \ + -fPIC -shared -fvisibility=default \ + -o $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec_nos_c_tre.so \ + $(LIBTERMUX_EXEC__NOS__C__OBJECT_FILES) \ + -l:libtermux-core_nos_c_tre.a + + @printf "\ntermux-exec-package: %s\n" "Building lib/libtermux-exec_nos_c_tre.a" + $(AR) rcs $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec_nos_c_tre.a $(LIBTERMUX_EXEC__NOS__C__OBJECT_FILES) +build-libtermux-exec-direct-ld-preload: + @mkdir -p $(LIB_BUILD_OUTPUT_DIR) + + @# Unlike `libtermux-exec_nos_c_tre.so` and `libtermux-exec_nos_c_tre.a`, all + @# symbols are hidden, except the exported functions with + @# `default` visibility with `__attribute__((visibility("default")))`, + @# defined in the `TermuxExecDirectLDPreloadEntryPoint.c` file meant to + @# be intercepted by `$LD_PRELOAD`. + @# `nm --demangle --dynamic --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/lib/libtermux-exec-direct-ld-preload.so` + @printf "\ntermux-exec-package: %s\n" "Building lib/libtermux-exec-direct-ld-preload" + $(CC) $(CFLAGS) $(LIBTERMUX_EXEC__NOS__C__CPPFLAGS) \ + -L$(LIB_BUILD_OUTPUT_DIR) $(LDFLAGS) -Wl,--exclude-libs=ALL \ + $(TERMUX__CONSTANTS__MACRO_FLAGS) \ + -fPIC -shared -fvisibility=hidden \ + -o $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec-direct-ld-preload.so \ + app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c \ + -l:libtermux-exec_nos_c_tre.a -l:libtermux-core_nos_c_tre.a + + @# By default, set `libtermux-exec-direct-ld-preload.so` as the + @# primary library variant exported in `$LD_PRELOAD` by copying it + @# to `libtermux-exec-ld-preload.so`. + @# Creating a symlink may have performance impacts. + cp -a $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec-direct-ld-preload.so $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec-ld-preload.so + + @# For backward compatibility, symlink `libtermux-exec.so` to + @# `libtermux-exec-ld-preload.so` so that older clients do not + @# break which have exported path to `libtermux-exec.so` in + @# `$LD_PRELOAD` via `login` script of older versions of + @# `termux-tools `package. + rm -f $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec.so + ln -s libtermux-exec-ld-preload.so $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec.so + + clean: - rm -f libtermux-exec.so tests/*-actual test-binary + rm -rf $(BUILD_OUTPUT_DIR) + +install: + @printf "termux-exec-package: %s\n" "Installing termux-exec-package in $(TERMUX_EXEC_PKG__INSTALL_PREFIX)" + install -d $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include + install -d $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib + install -d $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/libexec + + + rm -rf $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec + install -d $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec/termux + + cp -a lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec/termux/termux_exec__nos__c + install $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec_nos_c_tre.so $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec_nos_c_tre.so + install $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec_nos_c_tre.a $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec_nos_c_tre.a -install: libtermux-exec.so - install libtermux-exec.so $(DESTDIR)$(PREFIX)/lib/libtermux-exec.so + install $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec-ld-preload.so $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec-ld-preload.so + install $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec-direct-ld-preload.so $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec-direct-ld-preload.so + @# Use `cp` for symlink as `install` will copy the target regular file instead. + cp -a $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec.so $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec.so + + find $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec -type d -exec chmod 700 {} \; + find $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec -type f -exec chmod 600 {} \; + + + @printf "\ntermux-exec-package: %s\n\n" "Install termux-exec-package successful" uninstall: - rm -f $(DESTDIR)$(PREFIX)/lib/libtermux-exec.so + @printf "termux-exec-package: %s\n" "Uninstalling termux-exec-package from $(TERMUX_EXEC_PKG__INSTALL_PREFIX)" -on-device-tests: libtermux-exec.so - @LD_PRELOAD=${CURDIR}/libtermux-exec.so ./run-tests.sh + rm -rf $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec -format: - $(CLANG_FORMAT) -i $(C_SOURCE) -check: - $(CLANG_FORMAT) --dry-run $(C_SOURCE) - $(CLANG_TIDY) -warnings-as-errors='*' $(C_SOURCE) -- -DTERMUX_BASE_DIR=\"$(TERMUX_BASE_DIR)\" + rm -f $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec_nos_c_tre.so + rm -f $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec_nos_c_tre.a -test-binary: $(C_SOURCE) - $(CC) $(CFLAGS) $(LDFLAGS) $(C_SOURCE) -g -fsanitize=address -fno-omit-frame-pointer -DUNIT_TEST=1 -DTERMUX_BASE_DIR=\"$(TERMUX_BASE_DIR)\" -o test-binary + rm -f $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec-ld-preload.so + rm -f $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec-direct-ld-preload.so + rm -f $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec.so + + @printf "\ntermux-exec-package: %s\n\n" "Uninstall termux-exec-package successful" + + + +packaging-debian-build: all + termux-create-package $(DEBIAN_PACKAGING_BUILD_OUTPUT_DIR)/termux-exec-package.json + + + + + +format: + $(CLANG_FORMAT) -i app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c $(LIBTERMUX_EXEC__NOS__C__SOURCE_FILES) +check: + $(CLANG_FORMAT) --dry-run app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c $(LIBTERMUX_EXEC__NOS__C__SOURCE_FILES) + $(CLANG_TIDY) -warnings-as-errors='*' \ + app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c $(LIBTERMUX_EXEC__NOS__C__SOURCE_FILES) -- \ + $(LIBTERMUX_EXEC__NOS__C__CPPFLAGS) \ + $(TERMUX__CONSTANTS__MACRO_FLAGS) -deb: libtermux-exec.so - termux-create-package termux-exec-debug.json -unit-test: test-binary - ./test-binary -.PHONY: deb clean install uninstall test format check-format test +.PHONY: all pre-build build-termux-exec-main-app build-libtermux-exec_nos_c_tre build-libtermux-exec-direct-ld-preload clean install uninstall packaging-debian-build format check diff --git a/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c b/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c new file mode 100644 index 0000000..cc48f22 --- /dev/null +++ b/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c @@ -0,0 +1,96 @@ +#define _GNU_SOURCE +#include +#include +#include + +#include +#include +#include + +/** + * This file defines functions intercepted by `libtermux-exec-direct-ld-preload.so` using `$LD_PRELOAD`. + * + * All exported functions must explicitly enable `default` visibility + * with `__attribute__((visibility("default")))` as `libtermux-exec-direct-ld-preload.so` + * is compiled with `-fvisibility=hidden` so that no other internal + * functions are exported. + * + * You can check exported symbols for dynamic linking after building with: + * `nm --demangle --dynamic --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/lib/libtermux-exec-direct-ld-preload.so`. + */ + + + +void termuxExec_directLdPreload_initProcess() { + termuxExec_process_initProcess(TERMUX_EXEC_PKG__VERSION "+direct-ld-preload", NULL); +} + + + +__attribute__((visibility("default"))) +int execl(const char *name, const char *arg, ...) { + termuxExec_directLdPreload_initProcess(); + + va_list ap; + va_start(ap, arg); + int result = execlIntercept(true, ExecL, name, arg, ap); + va_end(ap); + return result; +} + +__attribute__((visibility("default"))) +int execlp(const char *name, const char *arg, ...) { + termuxExec_directLdPreload_initProcess(); + + va_list ap; + va_start(ap, arg); + int result = execlIntercept(true, ExecLP, name, arg, ap); + va_end(ap); + return result; +} + +__attribute__((visibility("default"))) +int execle(const char *name, const char *arg, ...) { + termuxExec_directLdPreload_initProcess(); + + va_list ap; + va_start(ap, arg); + int result = execlIntercept(true, ExecLE, name, arg, ap); + va_end(ap); + return result; +} + +__attribute__((visibility("default"))) +int execv(const char *name, char *const *argv) { + termuxExec_directLdPreload_initProcess(); + + return execvIntercept(true, name, argv); +} + +__attribute__((visibility("default"))) +int execvp(const char *name, char *const *argv) { + termuxExec_directLdPreload_initProcess(); + + return execvpIntercept(true, name, argv); +} + +__attribute__((visibility("default"))) +int execvpe(const char *name, char *const *argv, char *const *envp) { + termuxExec_directLdPreload_initProcess(); + + return execvpeIntercept(true, name, argv, envp); +} + +__attribute__((visibility("default"))) +int fexecve(int fd, char *const *argv, char *const *envp) { + termuxExec_directLdPreload_initProcess(); + + return fexecveIntercept(true, fd, argv, envp); +} + +__attribute__((visibility("default"))) +int execve(const char *name, char *const argv[], char *const envp[]) { + termuxExec_directLdPreload_initProcess(); + + return execveIntercept(true, name, argv, envp); +} diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/TermuxExecLibraryConfig.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/TermuxExecLibraryConfig.h new file mode 100644 index 0000000..e473201 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/TermuxExecLibraryConfig.h @@ -0,0 +1,24 @@ +#ifndef LIBTERMUX_EXEC__NOS__C__TERMUX_EXEC_LIBRARY_CONFIG___H +#define LIBTERMUX_EXEC__NOS__C__TERMUX_EXEC_LIBRARY_CONFIG___H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + + +/** Get `sIsRunningTests`. */ +bool libtermux_exec__nos__c__getIsRunningTests(); + +/** Set `sIsRunningTests`. */ +void libtermux_exec__nos__c__setIsRunningTests(bool isRunningTests); + + + +#ifdef __cplusplus +} +#endif + +#endif // LIBTERMUX_EXEC__NOS__C__TERMUX_EXEC_LIBRARY_CONFIG___H diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h new file mode 100644 index 0000000..66872b7 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h @@ -0,0 +1,278 @@ +#ifndef LIBTERMUX_EXEC__NOS__C__EXEC_INTERCEPT___H +#define LIBTERMUX_EXEC__NOS__C__EXEC_INTERCEPT___H + +#include + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + + +/* + * For the `execve()` system call, the kernel imposes a maximum length + * limit on script shebang including the `#!` characters at the start + * of a script. For Linux kernel `< 5.1`, the limit is `128` + * characters and for Linux kernel `>= 5.1`, the limit is `256` + * characters as per `BINPRM_BUF_SIZE` including the null `\0` + * terminator. + * + * If `libtermux-exec-ld-preload.so` is set in `LD_PRELOAD` and + * `TERMUX_EXEC__EXECVE_CALL__INTERCEPT` is enabled, then shebang limit + * is increased to `340` characters defined by + * `TERMUX__FILE_HEADER__BUFFER_SIZE` as shebang is read and script is + * passed to interpreter as an argument by `termux-exec` manually. + * Increasing limit to `340` also fixes issues for older Android kernel + * versions where limit is `128`. The limit is increased to `340`, + * because `BINPRM_BUF_SIZE` would be set based on the assumption that + * rootfs is at `/`, so we add Termux rootfs directory max length to it. + * + * - https://man7.org/linux/man-pages/man2/execve.2.html + * - https://en.wikipedia.org/wiki/Shebang_(Unix)#Character_interpretation + * - https://cs.android.com/android/kernel/superproject/+/0dc2b7de045e6dcfff9e0dfca9c0c8c8b10e1cf3:common/fs/binfmt_script.c;l=34 + * - https://cs.android.com/android/kernel/superproject/+/0dc2b7de045e6dcfff9e0dfca9c0c8c8b10e1cf3:common/include/linux/binfmts.h;l=64 + * - https://cs.android.com/android/kernel/superproject/+/0dc2b7de045e6dcfff9e0dfca9c0c8c8b10e1cf3:common/include/uapi/linux/binfmts.h;l=18 + * + * The running a script in `bash`, and the interpreter length is + * `>= 128` (`BINPRM_BUF_SIZE`) and `execve()` system call returns + * `ENOEXEC` (`Exec format error`), then `bash` will read the file + * and run it as `bash` shell commands. + * If interpreter length was `< 128` and `execve()` returned some + * other error than `ENOEXEC`, then `bash` will try to give a + * meaningful error. + * - If script was not executable: `bash: : Permission denied` + * - If script was a directory: `bash: : Is a directory` + * - If `ENOENT` was returned since interpreter file was not found: + * `bash: : cannot execute: required file not found` + * - If some unhandled errno was returned, like interpreter file was a directory: + * `bash: : : bad interpreter` + * + * - https://github.com/bminor/bash/blob/bash-5.2/execute_cmd.c#L5929 + * - https://github.com/bminor/bash/blob/bash-5.2/execute_cmd.c#L5964 + * - https://github.com/bminor/bash/blob/bash-5.2/execute_cmd.c#L5988 + * - https://github.com/bminor/bash/blob/bash-5.2/execute_cmd.c#L6048 + */ + +/** + * The max length for entire shebang line for the Linux kernel `>= 5.1` defined by `BINPRM_BUF_SIZE`. + * Add `FILE_HEADER__` scope to prevent conflicts by header importers. + * + * Default value: `256` + */ +#define TERMUX__FILE_HEADER__BINPRM_BUF_SIZE 256 + +/** + * The max length for interpreter path in the shebang for `termux-exec`. + * + * Default value: `340` + */ +#define TERMUX__FILE_HEADER__INTERPRETER_PATH___MAX_LEN (TERMUX__ROOTFS_DIR___MAX_LEN + TERMUX__FILE_HEADER__BINPRM_BUF_SIZE - 1) // The `- 1` is to only allow one null `\0` terminator. + +/** + * The max length for interpreter arg in the shebang for `termux-exec`. + * + * This is same as `TERMUX__FILE_HEADER__BINPRM_BUF_SIZE`. There is + * no way to divide `BINPRM_BUF_SIZE` between path and arg, so we give + * it full buffer size in case it needs it. + * + * Default value: `256` + */ +#define TERMUX__FILE_HEADER__INTERPRETER_ARG___MAX_LEN TERMUX__FILE_HEADER__BINPRM_BUF_SIZE + +/** + * The max length for entire shebang line for `termux-exec`. + * + * This is same as `TERMUX__FILE_HEADER__INTERPRETER_PATH___MAX_LEN`. + * + * Default value: `340` + */ +#define TERMUX__FILE_HEADER__BUFFER_SIZE TERMUX__FILE_HEADER__INTERPRETER_PATH___MAX_LEN + + + +/** + * The info for the file header of an executable file. + * + * - https://refspecs.linuxfoundation.org/elf/gabi4+/ch4.eheader.html + */ +struct TermuxFileHeaderInfo { + /** Whether executable is an ELF file instead of a script. */ + bool isElf; + + /** + * Whether the `Elf32_Ehdr.e_machine != EM_NATIVE`, i.e executable + * file is 32-bit binary on a 64-bit host. + */ + bool isNonNativeElf; + + /** + * The original interpreter path set in the executable file that is + * not normalized, absolutized or prefixed. + */ + char const *origInterpreterPath; + + /** + * The interpreter path set in the executable file that is + * normalized, absolutized and prefixed. + */ + char const *interpreterPath; + /** The underlying buffer for `interpreterPath`. */ + char interpreterPathBuffer[TERMUX__FILE_HEADER__INTERPRETER_PATH___MAX_LEN]; + + /** The arguments to the interpreter set in the executable file. */ + char const *interpreterArg; + /** The underlying buffer for `interpreterArg`. */ + char interpreterArgBuffer[TERMUX__FILE_HEADER__INTERPRETER_ARG___MAX_LEN]; +}; + + + +/** The host native architecture. */ +#ifdef __aarch64__ +#define EM_NATIVE EM_AARCH64 +#elif defined(__arm__) || defined(__thumb__) +#define EM_NATIVE EM_ARM +#elif defined(__x86_64__) +#define EM_NATIVE EM_X86_64 +#elif defined(__i386__) +#define EM_NATIVE EM_386 +#else +#error "unknown arch" +#endif + + +/** + * The list of variables that are unset by `modifyExecEnv()` if + * `unsetLdVarsFromEnv` is `true`. + */ +static const char *LD_VARS_TO_UNSET[] __attribute__ ((unused)) = { ENV_PREFIX__LD_LIBRARY_PATH, ENV_PREFIX__LD_PRELOAD }; +static int LD_VARS_TO_UNSET_SIZE __attribute__ ((unused)) = 2; + + +/** + * Intercept for the `execve()` method in `unistd.h`. + * + * If `isTermuxExecExecveInterceptEnabled()` returns `1`, then + * `execveIntercept()` will be called, otherwise `execveSyscall()`. + * + * - https://man7.org/linux/man-pages/man3/exec.3.html + */ +int execveIntercept(bool intercept, const char *executablePath, char *const argv[], char *const envp[]); + + + +/** + * Read file header from an executable file. + * + * @param label The label for errors. + * @param executablePath The path of the executable. + * @param buffer The header buffer. + * @param bufferSize The header buffer size. + * @return Returns the header length read, otherwise `-1` on other failures. + */ +int readFileHeader(const char *label, const char *executablePath, + char *buffer, size_t bufferSize); + +/** + * Inspect file header and set `TermuxFileHeaderInfo`. + * + * @param termuxPrefixDir The **normalized** path to termux prefix + * directory. If `NULL`, then path returned by + * `termux_prefixDir_getFromEnvOrDefault()` + * will be used by calling `termux_prefixDir_get()`. + * @param header The file header read from the executable file. + * The `TERMUX__FILE_HEADER__BUFFER_SIZE` should be used + * as buffer size when reading. + * @param headerLength The actual length of the header that was read. + * @param info The `TermuxFileHeaderInfo` to set. + */ +int inspectFileHeader(const char *termuxPrefixDir, char *header, size_t headerLength, + struct TermuxFileHeaderInfo *info); + +/** + * Check if file header is for an ELF file. + * + * @param header The file header read from the executable file. + * The `TERMUX__FILE_HEADER__BUFFER_SIZE` should be used + * as buffer size when reading. + * @param headerLength The actual length of the header that was read. + */ +bool isElfFile(char *header, size_t headerLength); + + + +/** + * Whether variables in `LD_VARS_TO_UNSET` should be unset before `exec()` + * to prevent issues when executing system binaries that are caused + * if they are set. + * + * @param isNonNativeElf The value for `TermuxFileHeaderInfo.isNonNativeElf` + * for the executable file. + * @param executablePath The **normalized** executable path to check. + * @return Returns `true` if `isNonNativeElf` equals `true` or + * `executablePath` starts with `/system/`, but does not equal + * `/system/bin/sh`, `system/bin/linker` or `/system/bin/linker64`. + * + */ +bool shouldUnsetLDVarsFromEnv(bool isNonNativeElf, const char *executablePath); + +/** + * Modify the environment for `execve()`. + * + * @param envp The current environment pointer. + * @param newEnvpPointer The new environment pointer to set. + * @param envTermuxProcSelfExe If set, then it will overwrite or + * set the `ENV_PREFIX__TERMUX_EXEC__PROC_SELF_EXE` + * env variable. + * @param unsetLdVarsFromEnv If `true`, then variables in + * `LD_VARS_TO_UNSET` will be unset. + * @return Returns `0` if successfully modified the env, otherwise + * `-1` on failures. Its the callers responsibility to call `free()` + * on the `newEnvpPointer` passed. + */ +int modifyExecEnv(char *const *envp, char ***newEnvpPointer, + char** envTermuxProcSelfExe, bool unsetLdVarsFromEnv); + + + +/** + * Modify the arguments for `execve()`. + * + * If `interpreterSet` is set, then `argv[0]` will be set to + * `TermuxFileHeaderInfo.origInterpreterPath`, otherwise the original + * `argv[0]` passed to `execve()` will be preserved. + * + * If `interpreterSet` is set, then `TermuxFileHeaderInfo.interpreterArg` + * will be appended if set, followed by the `origExecutablePath` + * passed to `execve()`. + * + * Any additional arguments to `execve()` will be appended after this. + * + * @param argv The current arguments pointer. + * @param newArgvPointer The new arguments pointer to set. + * @param origExecutablePath The originnal executable path passed to + * `execve()`. + * @param executablePath The **normalized** executable or interpreter + * path that will actually be executed. + * @param interpreterSet Whether a interpreter is set in the executable + * file. + * @param info The `TermuxFileHeaderInfo` for the executable file. + * @return Returns `0` if successfully modified the args, otherwise + * `-1` on failures. Its the callers responsibility to call `free()` + * on the `newArgvPointer` passed. + */ +int modifyExecArgs(char *const *argv, const char ***newArgvPointer, + const char *origExecutablePath, const char *executablePath, + bool interpreterSet, struct TermuxFileHeaderInfo *info); + + + +#ifdef __cplusplus +} +#endif + +#endif // LIBTERMUX_EXEC__NOS__C__EXEC_INTERCEPT___H diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.h new file mode 100644 index 0000000..55898b5 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.h @@ -0,0 +1,82 @@ +#ifndef LIBTERMUX_EXEC__NOS__C__EXEC_VARIANTS_INTERCEPT___H +#define LIBTERMUX_EXEC__NOS__C__EXEC_VARIANTS_INTERCEPT___H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + + +/** + * Intercepts for all the `exec()` family of functions in `unistd.h`, other + * than `execve()`, whose intercept `execveIntercept()` is declared + * in `ExecIntercept.h`. + * + * - https://man7.org/linux/man-pages/man3/exec.3.html + * + * These `exec()` variants end up calling `execve()` and are ported + * from `libc/bionic/exec.cpp`. + * + * For Android `< 14` intercepting `execve()` was enough. + * For Android `>= 14` requires intercepting the entire `exec()` family + * of functions. It might be related to the `3031a7e4` commit in `bionic`, + * in which `exec.cpp` added `memtag-stack` for `execve()` and shifted + * to calling `__execve()` internally in it. + * + * Intercepting the entire family should also solve some issues on older + * Android versions, check `libc/bionic/exec.cpp` git history. + * + * Tests for each `exec` family is done by `run_all_exec_wrappers_test` + * in `TermuxExecRuntimeBinaryTests.c`. + * + * - https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/bionic/exec.cpp + * - https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/SYSCALLS.TXT;l=68 + * - https://cs.android.com/android/_/android/platform/bionic/+/3031a7e45eb992d466610bec3bb589c41b74992b + */ + + +enum EXEC_VARIANTS { ExecVE, ExecL, ExecLP, ExecLE, ExecV, ExecVP, ExecVPE, FExecVE }; + +static const char * const EXEC_VARIANTS_STR[] = { + [ExecVE] = "execve", + [ExecL] = "execl", [ExecLP] = "execlp", [ExecLE] = "execle", + [ExecV] = "execv", [ExecVP] = "execvp", [ExecVPE] = "execvpe", + [FExecVE] = "fexecve" +}; + + +/** + * Intercept for the `execl()`, `execle()` and `execlp()` functions in `unistd.h` + * which redirects the call to `execve()`. + */ +int execlIntercept(bool wasIntercepted, int variant, const char *name, const char *argv0, va_list ap); + +/** + * Intercept for the `execv()` function in `unistd.h` which redirects the call to `execve()`. + */ +int execvIntercept(bool wasIntercepted, const char *name, char *const *argv); + +/** + * Intercept for the `execvp()` function in `unistd.h` which redirects the call to `execve()`. + */ +int execvpIntercept(bool wasIntercepted, const char *name, char *const *argv); + +/** + * Intercept for the `execvpe()` function in `unistd.h` which redirects the call to `execve()`. + */ +int execvpeIntercept(bool wasIntercepted, const char *name, char *const *argv, char *const *envp); + +/** + * Intercept for the `fexecve()` function in `unistd.h` which redirects the call to `execve()`. + */ +int fexecveIntercept(bool wasIntercepted, int fd, char *const *argv, char *const *envp); + + + +#ifdef __cplusplus +} +#endif + +#endif // LIBTERMUX_EXEC__NOS__C__EXEC_VARIANTS_INTERCEPT___H diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/os/process/termux_exec/TermuxExecProcess.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/os/process/termux_exec/TermuxExecProcess.h new file mode 100644 index 0000000..2ae5db7 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/os/process/termux_exec/TermuxExecProcess.h @@ -0,0 +1,23 @@ +#ifndef LIBTERMUX_EXEC__NOS__C__TERMUX_EXEC_PROCESS___H +#define LIBTERMUX_EXEC__NOS__C__TERMUX_EXEC_PROCESS___H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + + +int termuxExec_process_initProcess(const char *versionToLog, const char *logFilePath); +int termuxExec_process_initLogger(const char *versionToLog, const char *logFilePath); +void termuxExec_process_setIgnoreExit(bool state); +int termuxExec_process_exitProcess(); + + + +#ifdef __cplusplus +} +#endif + +#endif // LIBTERMUX_EXEC__NOS__C__TERMUX_EXEC_PROCESS___H diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h new file mode 100644 index 0000000..1b060e8 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h @@ -0,0 +1,84 @@ +#ifndef LIBTERMUX_EXEC__NOS__C__TERMUX_EXEC_SHELL_ENVIRONMENT___H +#define LIBTERMUX_EXEC__NOS__C__TERMUX_EXEC_SHELL_ENVIRONMENT___H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + + +/* + * Environment for `termux-exec`. + */ + +/** + * Environment variable for the log level for `termux-exec`. + * + * Type: `int` + * Default key: `TERMUX_EXEC__LOG_LEVEL` + * Default value: DEFAULT_LOG_LEVEL + * Values: + * - `0` (`OFF`) - Log nothing. + * - `1` (`NORMAL`) - Log error, warn and info messages and stacktraces. + * - `2` (`DEBUG`) - Log debug messages. + * - `3` (`VERBOSE`) - Log verbose messages. + * - `4` (`VVERBOSE`) - Log very verbose messages. + */ +#define ENV__TERMUX_EXEC__LOG_LEVEL TERMUX_ENV__S_TERMUX_EXEC "LOG_LEVEL" + + + + + +/** + * Termux environment variables `termux-exec` `execve()` call scope. + * + * Default value: `TERMUX_EXEC__EXECVE_CALL__` + */ +#define TERMUX_ENV__S_TERMUX_EXEC__EXECVE_CALL TERMUX_ENV__S_TERMUX_EXEC "EXECVE_CALL__" + +/** + * Environment variable for whether `termux-exec` should intercept + * `execve()` wrapper declared in `unistd.h`. + * + * Type: `string` + * Default key: `TERMUX_EXEC__EXECVE_CALL__INTERCEPT` + * Default value: ENV_DEF_VAL__TERMUX_EXEC__EXECVE_CALL__INTERCEPT + * Values: + * - `disable` - Intercept `execve()` will be disabled. + * - `enable` - Intercept `execve()` will be enabled. + */ +#define ENV__TERMUX_EXEC__EXECVE_CALL__INTERCEPT TERMUX_ENV__S_TERMUX_EXEC__EXECVE_CALL "INTERCEPT" +static const int ENV_DEF_VAL__TERMUX_EXEC__EXECVE_CALL__INTERCEPT = 1; + + +/** + * Returns the `termux-exec` config for `Logger` log level + * based on the `ENV__TERMUX_EXEC__LOG_LEVEL` env variable. + * + * @return Return the value if `ENV__TERMUX_EXEC__LOG_LEVEL` is + * set, otherwise defaults to `DEFAULT_LOG_LEVEL`. + */ +int termuxExec_logLevel_get(); + + + +/** + * Returns the `termux-exec` config for whether `execve` should be + * intercepted based on the `ENV__TERMUX_EXEC__EXECVE_CALL__INTERCEPT` env variable. + * + * @return Return `0` if `ENV__TERMUX_EXEC__EXECVE_CALL__INTERCEPT` is + * set to `disable`, `1` if set to `enable`, otherwise defaults to + * `1` (`enable`). + */ +int termuxExec_execveCall_intercept_get(); + + + +#ifdef __cplusplus +} +#endif + +#endif // LIBTERMUX_EXEC__NOS__C__TERMUX_EXEC_SHELL_ENVIRONMENT___H diff --git a/lib/termux-exec_nos_c_tre/src/TermuxExecLibraryConfig.c b/lib/termux-exec_nos_c_tre/src/TermuxExecLibraryConfig.c new file mode 100644 index 0000000..a38874a --- /dev/null +++ b/lib/termux-exec_nos_c_tre/src/TermuxExecLibraryConfig.c @@ -0,0 +1,15 @@ +#include + +#include + +static bool sIsRunningTests = false; + + + +bool libtermux_exec__nos__c__getIsRunningTests() { + return sIsRunningTests; +} + +void libtermux_exec__nos__c__setIsRunningTests(bool isRunningTests) { + sIsRunningTests = isRunningTests; +} diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c new file mode 100644 index 0000000..ee31875 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c @@ -0,0 +1,613 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static const char* LOG_TAG = "exec"; + + + +/** + * Call the `execve(2)` system call. + * + * - https://man7.org/linux/man-pages/man2/execve.2.html + */ +__attribute__((visibility("hidden"))) +int execveSyscall(const char *executablePath, char *const argv[], char *const envp[]); + +/** + * Intercept and make changes required for termux and then call + * `execveSyscall()` to execute the `execve(2)` system call. + */ +__attribute__((visibility("hidden"))) +int execveInterceptInternal(const char *origExecutablePath, char *const argv[], char *const envp[]); + + + +int execveIntercept(bool intercept, const char *executablePath, char *const argv[], char *const envp[]) { + bool debugLoggingEnabled = getCurrentLogLevel() >= LOG_LEVEL__NORMAL; + + if (debugLoggingEnabled) { + if (intercept) { + logErrorDebug(LOG_TAG, "<----- execve() intercepted ----->"); + } + logErrorVerbose(LOG_TAG, "executable = '%s'", executablePath); + int tmpArgvCount = 0; + while (argv[tmpArgvCount] != NULL) { + logErrorVerbose(LOG_TAG, " argv[%d] = '%s'", tmpArgvCount, argv[tmpArgvCount]); + tmpArgvCount++; + } + } + + int result; + if (termuxExec_execveCall_intercept_get() == 0) { + logErrorVerbose(LOG_TAG, "Intercept execve disabled"); + result = execveSyscall(executablePath, argv, envp); + } else { + logErrorVerbose(LOG_TAG, "Intercepting execve"); + result = execveInterceptInternal(executablePath, argv, envp); + } + + if (debugLoggingEnabled) { + int savedErrno = errno; + logErrorDebug(LOG_TAG, "<----- execve() failed ----->"); + errno = savedErrno; + } + + return result; +} + +int execveSyscall(const char *executablePath, char *const argv[], char *const envp[]) { + return syscall(SYS_execve, executablePath, argv, envp); +} + +int execveInterceptInternal(const char *origExecutablePath, char *const argv[], char *const envp[]) { + bool debugLoggingEnabled = getCurrentLogLevel() >= LOG_LEVEL__NORMAL; + bool verboseLoggingEnabled = getCurrentLogLevel() >= LOG_LEVEL__VERBOSE; + + // - We normalize the path to remove `.` and `..` path components, + // and duplicate path separators `//`, but without resolving symlinks. + // For instance, `$TERMUX__PREFIX/bin/ls` is a symlink to `$TERMUX__PREFIX/bin/coreutils`, + // but we need to execute `$TERMUX__PREFIX/bin/ls` `/system/bin/linker $TERMUX__PREFIX/bin/ls` + // so that coreutils knows what to execute. + // - For an absolute path, we need to normalize first so that an + // unnormalized prefix like `/usr/./bin` is replaced with `/usr/bin` + // so that `termuxPrefixPath()` can successfully match it to + // replace prefix with termux rootfs prefix. + // - For a relative path, we do not replace prefix with termux rootfs + // prefix, but instead prefix the current working directory (`cwd`). + // If `cwd` is set to `/bin` and `./sh` is executed, then + // `/bin/sh` should be executed instead of `$TERMUX__PREFIX/bin/sh`. + // Moreover, to handle the case where the executable path contains + // double dot `..` path components like `../sh`, we need to + // prefix the `cwd` first and then normalize the path, otherwise + // `normalizePath()` will return `null`, as unknown path + // components cannot be removed from a path. + // If instead on returning `null`, `normalizePath()` just + // removed the extra leading double dot components from the start + // and then we prefixed with `cwd`, then final path will be wrong + // since double dot path components would have been removed before + // they could be used to remove path components of the `cwd`. + // - $TERMUX_EXEC__PROC_SELF_EXE will be later set to the processed path + // (normalized/absolutized/prefixed) that will actually be executed. + char executablePathBuffer[strlen(origExecutablePath) + 1]; + strcpy(executablePathBuffer, origExecutablePath); + const char *executablePath = executablePathBuffer; + + + char processedExecutablePathBuffer[PATH_MAX]; + if (executablePath[0] == '/') { + // If path is absolute, then normalize first and then replace termux prefix. + executablePath = normalizePath(executablePathBuffer, false, true); + if (executablePath == NULL) { + logStrerrorDebug(LOG_TAG, "Failed to normalize executable path '%s'", origExecutablePath); + return -1; + } + + if (verboseLoggingEnabled && strcmp(origExecutablePath, executablePath) != 0) { + logErrorVVerbose(LOG_TAG, "normalized_executable: '%s'", executablePath); + } + const char *normalizedExecutablePath = executablePath; + + + executablePath = termuxPrefixPath(LOG_TAG, NULL, executablePath, + processedExecutablePathBuffer, sizeof(processedExecutablePathBuffer)); + if (executablePath == NULL) { + logStrerrorDebug(LOG_TAG, "Failed to prefix normalized executable path '%s'", normalizedExecutablePath); + return -1; + } + + if (verboseLoggingEnabled && strcmp(normalizedExecutablePath, executablePath) != 0) { + logErrorVVerbose(LOG_TAG, "prefixed_executable: '%s'", executablePath); + } + } else { + // If path is relative, then absolutize first and then normalize. + executablePath = absolutizePath(executablePath, + processedExecutablePathBuffer, sizeof(processedExecutablePathBuffer)); + if (executablePath == NULL) { + logStrerrorDebug(LOG_TAG, "Failed to convert executable path '%s' to an absolute path", origExecutablePath); + return -1; + } + + if (verboseLoggingEnabled && strcmp(origExecutablePath, executablePath) != 0) { + logErrorVVerbose(LOG_TAG, "absolutized_executable: '%s'", executablePath); + } + + + char absoluteExecutablePathBuffer[strlen(processedExecutablePathBuffer) + 1]; + strcpy(absoluteExecutablePathBuffer, processedExecutablePathBuffer); + + executablePath = normalizePath(processedExecutablePathBuffer, false, true); + if (executablePath == NULL) { + logStrerrorDebug(LOG_TAG, "Failed to normalize absolutized executable path '%s'", absoluteExecutablePathBuffer); + return -1; + } + + if (verboseLoggingEnabled && strcmp(absoluteExecutablePathBuffer, executablePath) != 0) { + logErrorVVerbose(LOG_TAG, "normalized_executable: '%s'", executablePath); + } + } + + const char *processedExecutablePath = executablePath; + + // - https://man7.org/linux/man-pages/man2/access.2.html + if (access(executablePath, X_OK) != 0) { + // Error out if the file does not exist or is not executable + // fd paths like to a pipes should not be executable either and + // they cannot be seek-ed back if interpreter were to be read. + // - https://github.com/bminor/bash/blob/bash-5.2/shell.c#L1649 + logStrerrorDebug(LOG_TAG, "Failed to access executable path '%s'", processedExecutablePath); + return -1; + } + + + + char header[TERMUX__FILE_HEADER__BUFFER_SIZE]; + ssize_t headerLength = readFileHeader("executable", executablePath, header, sizeof(header)); + if (headerLength < 0) { + return headerLength; + } + + + struct TermuxFileHeaderInfo info = { + .interpreterPath = NULL, + .interpreterArg = NULL, + }; + + if (inspectFileHeader(NULL, header, headerLength, &info) != 0) { + return -1; + } + + bool interpreterSet = info.interpreterPath != NULL; + if (!info.isElf && !interpreterSet) { + errno = ENOEXEC; + logStrerrorDebug(LOG_TAG, "Not an ELF or no shebang in executable path '%s'", processedExecutablePath); + return -1; + } + + if (interpreterSet) { + executablePath = info.interpreterPath; + } + + + + bool modifyEnv = false; + bool unsetLdVarsFromEnv = shouldUnsetLDVarsFromEnv(info.isNonNativeElf, executablePath); + logErrorVVerbose(LOG_TAG, "unset_ld_vars_from_env: '%d'", unsetLdVarsFromEnv); + + if (unsetLdVarsFromEnv && areVarsInEnv(envp, LD_VARS_TO_UNSET, LD_VARS_TO_UNSET_SIZE)) { + modifyEnv = true; + } + + logErrorVVerbose(LOG_TAG, "modify_env: '%d'", modifyEnv); + + + + char **newEnvp = NULL; + if (modifyEnv) { + if (modifyExecEnv(envp, &newEnvp, &envTermuxProcSelfExe, unsetLdVarsFromEnv) != 0 || + newEnvp == NULL) { + logErrorDebug(LOG_TAG, "Failed to create modified exec env"); + free(envTermuxProcSelfExe); + return -1; + } + + envp = newEnvp; + } + + + + const bool modifyArgs = interpreterSet; + logErrorVVerbose(LOG_TAG, "modify_args: '%d'", modifyArgs); + + const char **newArgv = NULL; + if (modifyArgs) { + if (modifyExecArgs(argv, &newArgv, origExecutablePath, executablePath, + interpreterSet, &info) != 0 || + newArgv == NULL) { + logErrorDebug(LOG_TAG, "Failed to create modified exec args"); + free(envTermuxProcSelfExe); + free(newEnvp); + return -1; + } + + argv = (char **) newArgv; + } + + + + if (debugLoggingEnabled) { + logErrorVerbose(LOG_TAG, "Calling syscall execve"); + logErrorVerbose(LOG_TAG, "executable = '%s'", executablePath); + int tmpArgvCount = 0; + int arg_count = 0; + while (argv[tmpArgvCount] != NULL) { + logErrorVerbose(LOG_TAG, " argv[%d] = '%s'", arg_count++, argv[tmpArgvCount]); + tmpArgvCount++; + } + } + + int syscallReturnValue = execveSyscall(executablePath, argv, envp); + int savedErrno = errno; + logStrerrorDebug(LOG_TAG, "execve() syscall failed for executable path '%s'", executablePath); + free(envTermuxProcSelfExe); + free(newEnvp); + free(newArgv); + errno = savedErrno; + return syscallReturnValue; +} + + + +int readFileHeader(const char *label, const char *executablePath, + char *buffer, size_t bufferSize) { + // - https://man7.org/linux/man-pages/man2/open.2.html + int fd = open(executablePath, O_RDONLY); + if (fd == -1) { + errno = ENOENT; + logStrerrorDebug(LOG_TAG, "Failed to open %s path '%s'", label, executablePath); + return -1; + } + + + ssize_t headerLength = read(fd, buffer, bufferSize - 1); + // Ensure read was successful, path could be a directory and EISDIR will be returned. + // - https://man7.org/linux/man-pages/man2/read.2.html + if (headerLength < 0) { + logStrerrorDebug(LOG_TAG, "Failed to read %s path '%s'", label, executablePath); + return -1; + } + close(fd); + + return headerLength; +} + +int inspectFileHeader(const char *termuxPrefixDir, char *header, size_t headerLength, + struct TermuxFileHeaderInfo *info) { + if (isElfFile(header, headerLength)) { + info->isElf = true; + Elf32_Ehdr *ehdr = (Elf32_Ehdr *)header; + if (ehdr->e_machine != EM_NATIVE) { + info->isNonNativeElf = true; + } + return 0; + } + + if (headerLength < 3 || !(header[0] == '#' && header[1] == '!')) { + return 0; + } + + bool isRunningTests = libtermux_exec__nos__c__getIsRunningTests(); + + // Check if the header contains a newline to end the shebang line. + char *newlineIndex = memchr(header, '\n', headerLength); + if (newlineIndex == NULL) { + return 0; + } + + // Strip whitespace at end of shebang. + while (*(newlineIndex - 1) == ' ') { + newlineIndex--; + } + + // Null terminate the shebang line. + *newlineIndex = 0; + + // Skip whitespace to find interpreter start. + char const *interpreter = header + 2; + while (*interpreter == ' ') { + interpreter++; + } + if (interpreter == newlineIndex) { + // Just a blank line up until the newline. + return 0; + } + + // Check for whitespace following the interpreter. + char *whitespaceIndex = strchr(interpreter, ' '); + if (whitespaceIndex != NULL) { + // Null-terminate the interpreter string. + *whitespaceIndex = 0; + + // Find start of argument. + char *interpreterArg = whitespaceIndex + 1; + while (*interpreterArg != 0 && *interpreterArg == ' ') { + interpreterArg++; + } + if (interpreterArg != newlineIndex) { + size_t interpreterArgBufferSize = sizeof(info->interpreterArgBuffer); + + size_t interpreterArgLength = strlen(interpreterArg); + if (interpreterArgBufferSize <= interpreterArgLength) { + if (!isRunningTests) { + logErrorDebug(LOG_TAG, "The interpreter argument '%s' with length '%zu' is too long to fit in the buffer with size '%zu'", + interpreterArg, interpreterArgLength, interpreterArgBufferSize); + } + errno = ENAMETOOLONG; + return -1; + } + + strcpy(info->interpreterArgBuffer, interpreterArg); + info->interpreterArg = info->interpreterArgBuffer; + } + } + + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "interpreter_path: '%s'", interpreter); + } + + // argv[0] must be set to the original interpreter set in the file even + // if it is a relative path and its absolute path is to be executed. + info->origInterpreterPath = interpreter; + + + + bool verboseLoggingEnabled = getCurrentLogLevel() >= LOG_LEVEL__VERBOSE; + (void)verboseLoggingEnabled; + size_t interpreterPathBufferSize = sizeof(info->interpreterPathBuffer); + + char interpreterPathBuffer[strlen(interpreter) + 1]; + strcpy(interpreterPathBuffer, interpreter); + char *interpreterPath = interpreterPathBuffer; + + + if (interpreterPath[0] == '/') { + // If path is absolute, then normalize first and then replace termux prefix. + interpreterPath = normalizePath(interpreterPath, false, true); + if (interpreterPath == NULL) { + logStrerrorDebug(LOG_TAG, "Failed to normalize interpreter path '%s'", info->origInterpreterPath); + return -1; + } + if (!isRunningTests) { + if (verboseLoggingEnabled && strcmp(info->origInterpreterPath, interpreterPath) != 0) { + logErrorVVerbose(LOG_TAG, "normalized_interpreter: '%s'", interpreterPath); + } + } + + + const char *normalizedInterpreterPath = interpreterPath; + + info->interpreterPath = termuxPrefixPath(LOG_TAG, termuxPrefixDir, + interpreterPath, info->interpreterPathBuffer, interpreterPathBufferSize); + if (info->interpreterPath == NULL) { + logStrerrorDebug(LOG_TAG, "Failed to prefix normalized interpreter path '%s'", normalizedInterpreterPath); + return -1; + } + + if (!isRunningTests) { + if (verboseLoggingEnabled && strcmp(normalizedInterpreterPath, info->interpreterPath) != 0) { + logErrorVVerbose(LOG_TAG, "prefixed_interpreter: '%s'", info->interpreterPath); + } + } + } else { + char processedInterpreterPathBuffer[PATH_MAX]; + + // If path is relative, then absolutize first and then normalize. + interpreterPath = absolutizePath(interpreterPath, + processedInterpreterPathBuffer, sizeof(processedInterpreterPathBuffer)); + if (interpreterPath == NULL) { + logStrerrorDebug(LOG_TAG, "Failed to convert interpreter path '%s' to an absolute path", info->origInterpreterPath); + return -1; + } + + if (!isRunningTests) { + if (verboseLoggingEnabled && strcmp(info->origInterpreterPath, interpreterPath) != 0) { + logErrorVVerbose(LOG_TAG, "absolute_interpreter: '%s'", interpreterPath); + } + } + + + char absoluteInterpreterPathBuffer[strlen(processedInterpreterPathBuffer) + 1]; + strcpy(absoluteInterpreterPathBuffer, processedInterpreterPathBuffer); + + interpreterPath = normalizePath(processedInterpreterPathBuffer, false, true); + if (interpreterPath == NULL) { + logStrerrorDebug(LOG_TAG, "Failed to normalize absolutized interpreter path '%s'", absoluteInterpreterPathBuffer); + return -1; + } + + if (!isRunningTests) { + if (verboseLoggingEnabled && strcmp(absoluteInterpreterPathBuffer, interpreterPath) != 0) { + logErrorVVerbose(LOG_TAG, "normalized_interpreter: '%s'", interpreterPath); + } + } + + + size_t processedInterpreterPathLength = strlen(interpreterPath); + if (interpreterPathBufferSize <= processedInterpreterPathLength) { + if (!isRunningTests) { + logErrorDebug(LOG_TAG, "The processed interpreter path '%s' with length '%zu' is too long to fit in the buffer with size '%zu'", + interpreterPath, processedInterpreterPathLength, interpreterPathBufferSize); + } + errno = ENAMETOOLONG; + return -1; + } + + strcpy(info->interpreterPathBuffer, interpreterPath); + info->interpreterPath = info->interpreterPathBuffer; + } + + + if (!isRunningTests) { + if (verboseLoggingEnabled && info->interpreterArg != NULL) { + logErrorVVerbose(LOG_TAG, "interpreter_arg: '%s'", info->interpreterArg); + } + } + + return 0; +} + +bool isElfFile(char *header, size_t headerLength) { + return headerLength >= 20 && !memcmp(header, ELFMAG, SELFMAG); +} + + + +bool shouldUnsetLDVarsFromEnv(bool isNonNativeElf, const char *executablePath) { + return isNonNativeElf || + (stringStartsWith(executablePath, "/system/") && + strcmp(executablePath, "/system/bin/sh") != 0 && + strcmp(executablePath, "/system/bin/linker") != 0 && + strcmp(executablePath, "/system/bin/linker64") != 0); +} + +int modifyExecEnv(char *const *envp, char ***newEnvpPointer, + char** envTermuxProcSelfExe, bool unsetLdVarsFromEnv) { + int envCount = 0; + while (envp[envCount] != NULL) { + envCount++; + } + + // Allocate new environment variable array. Size + 2 since + // we might perhaps append a TERMUX_EXEC__PROC_SELF_EXE variable and + // we will also NULL terminate. + size_t newEnvpSize = (sizeof(char *) * (envCount + 2)); + void* result = malloc(newEnvpSize); + if (result == NULL) { + logStrerrorDebug(LOG_TAG, "The malloc called failed for new envp with size '%zu'", newEnvpSize); + return -1; + } + + char **newEnvp = (char **) result; + *newEnvpPointer = newEnvp; + + bool isRunningTests = libtermux_exec__nos__c__getIsRunningTests(); + + bool alreadyFoundProcSelfExe = false; + int index = 0; + for (int i = 0; i < envCount; i++) { + if (stringStartsWith(envp[i], ENV_PREFIX__TERMUX_EXEC__PROC_SELF_EXE)) { + if (envTermuxProcSelfExe != NULL && *envTermuxProcSelfExe != NULL) { + newEnvp[index++] = *envTermuxProcSelfExe; + alreadyFoundProcSelfExe = true; + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "Overwrite '%s'", *envTermuxProcSelfExe); + } + } + else { + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "Unset '%s'", envp[i]); + } + } + } else { + bool keep = true; + + if (unsetLdVarsFromEnv) { + for (int j = 0; j < LD_VARS_TO_UNSET_SIZE; j++) { + if (stringStartsWith(envp[i], LD_VARS_TO_UNSET[j])) { + keep = false; + } + } + } + + if (keep) { + newEnvp[index++] = envp[i]; + } + else { + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "Unset '%s'", envp[i]); + } + } + } + } + + if (envTermuxProcSelfExe != NULL && *envTermuxProcSelfExe != NULL && !alreadyFoundProcSelfExe) { + newEnvp[index++] = *envTermuxProcSelfExe; + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "Set '%s'", *envTermuxProcSelfExe); + } + } + + // Null terminate. + newEnvp[index] = NULL; + + return 0; +} + + + +int modifyExecArgs(char *const *argv, const char ***newArgvPointer, + const char *origExecutablePath, const char *executablePath, + bool interpreterSet, struct TermuxFileHeaderInfo *info) { + int argsCount = 0; + while (argv[argsCount] != NULL) { + argsCount++; + } + + size_t newArgvSize = (sizeof(char *) * (argsCount + 2)); + void* result = malloc(newArgvSize); + if (result == NULL) { + logStrerrorDebug(LOG_TAG, "The malloc called failed for new argv with size '%zu'", newArgvSize); + return -1; + } + + const char **newArgv = (const char **) result; + *newArgvPointer = newArgv; + + int index = 0; + + if (interpreterSet) { + // Use original interpreter path set in executable file as is. + newArgv[index++] = info->origInterpreterPath; + } else { + // Preserver original `argv[0]` to `execve()`. + newArgv[index++] = argv[0]; + } + + // Add interpreter argument and script path if executing a script with shebang. + if (interpreterSet) { + if (info->interpreterArg != NULL) { + newArgv[index++] = info->interpreterArg; + } + newArgv[index++] = origExecutablePath; + } + + for (int i = 1; i < argsCount; i++) { + newArgv[index++] = argv[i]; + } + + // Null terminate. + newArgv[index] = NULL; + + return 0; +} diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.c b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.c new file mode 100644 index 0000000..21abba1 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.c @@ -0,0 +1,185 @@ +/*- + * Copyright (c) 1991, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +static const char* LOG_TAG = "exec"; + +int execlIntercept(bool wasIntercepted, int variant, const char *name, const char *argv0, va_list ap) { + if (wasIntercepted) { + logErrorDebug(LOG_TAG, "<----- %s() intercepted ----->", EXEC_VARIANTS_STR[variant]); + } + + // Count the arguments. + va_list count_ap; + va_copy(count_ap, ap); + size_t n = 1; + while (va_arg(count_ap, char *) != NULL) { + ++n; + } + va_end(count_ap); + + // Construct the new argv. + char *argv[n + 1]; + argv[0] = (char *)argv0; + n = 1; + while ((argv[n] = va_arg(ap, char *)) != NULL) { + ++n; + } + + // Collect the argp too. + char **argp = (variant == ExecLE) ? va_arg(ap, char **) : environ; + + va_end(ap); + + return (variant == ExecLP) ? execvpIntercept(false, name, argv) : execveIntercept(false, name, argv, argp); +} + + +int execvIntercept(bool wasIntercepted, const char *name, char *const *argv) { + if (wasIntercepted) { + logErrorDebug(LOG_TAG, "<----- execv() intercepted ----->"); + } + + return execveIntercept(false, name, argv, environ); +} + +int execvpIntercept(bool wasIntercepted, const char *name, char *const *argv) { + if (wasIntercepted) { + logErrorDebug(LOG_TAG, "<----- execvp() intercepted ----->"); + } + + return execvpeIntercept(false, name, argv, environ); +} + +int __exec_as_script(const char *buf, char *const *argv, char *const *envp) { + size_t arg_count = 1; + while (argv[arg_count] != NULL) + ++arg_count; + + const char *script_argv[arg_count + 2]; + script_argv[0] = "sh"; + script_argv[1] = buf; + memcpy(script_argv + 2, argv + 1, arg_count * sizeof(char *)); + return execveIntercept(false, _PATH_BSHELL, (char **const)script_argv, envp); +} + +int execvpeIntercept(bool wasIntercepted, const char *name, char *const *argv, char *const *envp) { + if (wasIntercepted) { + logErrorDebug(LOG_TAG, "<----- execvpe() intercepted ----->"); + } + + // Do not allow null name. + if (name == NULL || *name == '\0') { + errno = ENOENT; + return -1; + } + + // If it's an absolute or relative path name, it's easy. + if (strchr(name, '/') && execveIntercept(false, name, argv, envp) == -1) { + if (errno == ENOEXEC) + return __exec_as_script(name, argv, envp); + return -1; + } + + // Get the path we're searching. + const char *path = getenv("PATH"); + if (path == NULL) + path = _PATH_DEFPATH; + + // Make a writable copy. + size_t len = strlen(path) + 1; + char writable_path[len]; + memcpy(writable_path, path, len); + + bool saw_EACCES = false; + + // Try each element of $PATH in turn... + char *strsep_buf = writable_path; + const char *dir; + while ((dir = strsep(&strsep_buf, ":"))) { + // It's a shell path: double, leading and trailing colons + // mean the current directory. + if (*dir == '\0') + dir = "."; + + size_t dir_len = strlen(dir); + size_t name_len = strlen(name); + + char buf[dir_len + 1 + name_len + 1]; + mempcpy(mempcpy(mempcpy(buf, dir, dir_len), "/", 1), name, name_len + 1); + + execveIntercept(false, buf, argv, envp); + switch (errno) { + case EISDIR: + case ELOOP: + case ENAMETOOLONG: + case ENOENT: + case ENOTDIR: + break; + case ENOEXEC: + return __exec_as_script(buf, argv, envp); + return -1; + case EACCES: + saw_EACCES = true; + break; + default: + return -1; + } + } + if (saw_EACCES) + errno = EACCES; + return -1; +} + +int fexecveIntercept(bool wasIntercepted, int fd, char *const *argv, char *const *envp) { + if (wasIntercepted) { + logErrorDebug(LOG_TAG, "<----- fexecve() intercepted ----->"); + } + + char buf[40]; + snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd); + execveIntercept(false, buf, argv, envp); + if (errno == ENOENT) + errno = EBADF; + return -1; +} diff --git a/lib/termux-exec_nos_c_tre/src/termux/os/process/termux_exec/TermuxExecProcess.c b/lib/termux-exec_nos_c_tre/src/termux/os/process/termux_exec/TermuxExecProcess.c new file mode 100644 index 0000000..61b658a --- /dev/null +++ b/lib/termux-exec_nos_c_tre/src/termux/os/process/termux_exec/TermuxExecProcess.c @@ -0,0 +1,73 @@ +#include + +#include +#include + +#include +#include + +static bool sInitLogger = true; +static bool sIgnoreExit = false; + + + +int termuxExec_process_initProcess(const char *versionToLog, const char *logFilePath) { + // Sometimes when a process starts, errno is set to values like + // EINVAL (22) and (ECHILD 10). Noticed on Android 11 (aarch64) and + // Android 14 (x86_64). + // It is not being caused by `termux-exec` as it happens even + // without `LD_PRELOAD` being set. + // Moreover, errno is 0 before `execveSyscall()` is called by + // `execveIntercept()` to replace the process, but in the `main()` + // of new process, errno is not zero, so something happens during + // the `syscall()` itself or in callers of `main()`. And manually + // setting errno before `execveSyscall()` does not transfer it + // to `main()` of new process. + // Programs should set errno to `0` at init themselves. + // We unset it here since programs should have already handled their + // errno if it was set by something else and `termux-exec` library + // also has checks to error out if errno is set in various places, + // like initially in `stringToInt()` called by `termuxExec_logLevel_get()`. + // Saving errno is useless as it will not be transferred anyways. + // - https://wiki.sei.cmu.edu/confluence/display/c/ERR30-C.+Take+care+when+reading+errno + errno = 0; + + return termuxExec_process_initLogger(versionToLog, logFilePath); +} + +int termuxExec_process_initLogger(const char *versionToLog, const char *logFilePath) { + if (sInitLogger) { + setDefaultLogTagAndPrefix(TERMUX__LNAME); + setCurrentLogLevel(termuxExec_logLevel_get()); + setCacheLogPid(true); + if (logFilePath != NULL) { + setLogFormatMode(LOG_FORMAT_MODE__PID_PRIORITY_TAG_AND_MESSAGE); + setLoggerImpl(&sFileLoggerImpl); + if (setLogFilePath(logFilePath) == -1) { + return -1; + } + } else { + setLogFormatMode(LOG_FORMAT_MODE__PID_PRIORITY_TAG_AND_MESSAGE); + } + sInitLogger = false; + + if (versionToLog != NULL) { + logErrorVVerbose("", "TERMUX_EXEC__VERSION: '%s'", versionToLog); + } + } + return 0; +} + + + +void termuxExec_process_setIgnoreExit(bool state) { + sIgnoreExit = state; +} + +int termuxExec_process_exitProcess() { + if (!sIgnoreExit) { + closeLogFile(); + sInitLogger = true; + } + return 0; +} diff --git a/lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c b/lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c new file mode 100644 index 0000000..a4ac2dd --- /dev/null +++ b/lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c @@ -0,0 +1,32 @@ +#define _GNU_SOURCE +#include +#include +#include +#include + +#include +#include +#include + +#include + + + +int termuxExec_logLevel_get() { + return getLogLevelFromEnv(ENV__TERMUX_EXEC__LOG_LEVEL); +} + + + +int termuxExec_execveCall_intercept_get() { + int def = ENV_DEF_VAL__TERMUX_EXEC__EXECVE_CALL__INTERCEPT; + const char* value = getenv(ENV__TERMUX_EXEC__EXECVE_CALL__INTERCEPT); + if (value == NULL || strlen(value) < 1) { + return def; + } else if (strcmp(value, "disable") == 0) { + return 0; + } else if (strcmp(value, "enable") == 0) { + return 1; + } + return def; +} diff --git a/packaging/debian/termux-exec-package.json.in b/packaging/debian/termux-exec-package.json.in new file mode 100644 index 0000000..7fc4c96 --- /dev/null +++ b/packaging/debian/termux-exec-package.json.in @@ -0,0 +1,20 @@ +{ + "control": { + "Package": "termux-exec", + "Version": "@TERMUX_EXEC_PKG__VERSION@", + "Architecture": "@TERMUX_EXEC_PKG__ARCH@", + "Maintainer": "@termux", + "Homepage": "https://github.com/termux/termux-exec-package", + "Description": "Utils and libraries for Termux exec including a LD_PRELOAD shared library for proper functioning of the Termux execution environment" + }, + + "installation_prefix": "@TERMUX__PREFIX@", + "control_files_dir": "build/output/packaging/debian", + + "data_files": { + "": { + "source": "build/output/usr", + "source_recurse": true + } + } +} diff --git a/src/exec-variants.c b/src/exec-variants.c deleted file mode 100644 index 672793d..0000000 --- a/src/exec-variants.c +++ /dev/null @@ -1,154 +0,0 @@ -// These exec variants, which ends up calling execve(), comes from bionic: -// https://android.googlesource.com/platform/bionic/+/refs/heads/main/libc/bionic/exec.cpp -// -// For some reason these are only necessary starting with Android 14 - before -// that intercepting execve() is enough. -// -// See the test-program.c for how to test the different variants. - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include - -enum { ExecL, ExecLE, ExecLP }; - -static int __exec_as_script(const char *buf, char *const *argv, char *const *envp) { - size_t arg_count = 1; - while (argv[arg_count] != NULL) - ++arg_count; - - const char *script_argv[arg_count + 2]; - script_argv[0] = "sh"; - script_argv[1] = buf; - memcpy(script_argv + 2, argv + 1, arg_count * sizeof(char *)); - return execve(_PATH_BSHELL, (char **const)script_argv, envp); -} - -int execv(const char *name, char *const *argv) { return execve(name, argv, environ); } - -int execvp(const char *name, char *const *argv) { return execvpe(name, argv, environ); } - -int execvpe(const char *name, char *const *argv, char *const *envp) { - // if (name == NULL || *name == '\0') { errno = ENOENT; return -1; } - - // If it's an absolute or relative path name, it's easy. - if (strchr(name, '/') && execve(name, argv, envp) == -1) { - if (errno == ENOEXEC) - return __exec_as_script(name, argv, envp); - return -1; - } - - // Get the path we're searching. - const char *path = getenv("PATH"); - if (path == NULL) - path = _PATH_DEFPATH; - - // Make a writable copy. - size_t len = strlen(path) + 1; - char writable_path[len]; - memcpy(writable_path, path, len); - - bool saw_EACCES = false; - - // Try each element of $PATH in turn... - char *strsep_buf = writable_path; - const char *dir; - while ((dir = strsep(&strsep_buf, ":"))) { - // It's a shell path: double, leading and trailing colons - // mean the current directory. - if (*dir == '\0') - dir = "."; - - size_t dir_len = strlen(dir); - size_t name_len = strlen(name); - - char buf[dir_len + 1 + name_len + 1]; - mempcpy(mempcpy(mempcpy(buf, dir, dir_len), "/", 1), name, name_len + 1); - - execve(buf, argv, envp); - switch (errno) { - case EISDIR: - case ELOOP: - case ENAMETOOLONG: - case ENOENT: - case ENOTDIR: - break; - case ENOEXEC: - return __exec_as_script(buf, argv, envp); - return -1; - case EACCES: - saw_EACCES = true; - break; - default: - return -1; - } - } - if (saw_EACCES) - errno = EACCES; - return -1; -} - -static int __execl(int variant, const char *name, const char *argv0, va_list ap) { - // Count the arguments. - va_list count_ap; - va_copy(count_ap, ap); - size_t n = 1; - while (va_arg(count_ap, char *) != NULL) { - ++n; - } - va_end(count_ap); - - // Construct the new argv. - char *argv[n + 1]; - argv[0] = (char *)argv0; - n = 1; - while ((argv[n] = va_arg(ap, char *)) != NULL) { - ++n; - } - - // Collect the argp too. - char **argp = (variant == ExecLE) ? va_arg(ap, char **) : environ; - - va_end(ap); - - return (variant == ExecLP) ? execvp(name, argv) : execve(name, argv, argp); -} - -int execl(const char *name, const char *arg, ...) { - va_list ap; - va_start(ap, arg); - int result = __execl(ExecL, name, arg, ap); - va_end(ap); - return result; -} - -int execle(const char *name, const char *arg, ...) { - va_list ap; - va_start(ap, arg); - int result = __execl(ExecLE, name, arg, ap); - va_end(ap); - return result; -} - -int execlp(const char *name, const char *arg, ...) { - va_list ap; - va_start(ap, arg); - int result = __execl(ExecLP, name, arg, ap); - va_end(ap); - return result; -} - -int fexecve(int fd, char *const *argv, char *const *envp) { - char buf[40]; - snprintf(buf, sizeof(buf), "/proc/self/fd/%d", fd); - execve(buf, argv, envp); - if (errno == ENOENT) - errno = EBADF; - return -1; -} diff --git a/src/termux-exec.c b/src/termux-exec.c deleted file mode 100644 index 1f8037d..0000000 --- a/src/termux-exec.c +++ /dev/null @@ -1,238 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef TERMUX_BASE_DIR -# define TERMUX_BASE_DIR "/data/data/com.termux/files" -#endif - -#ifndef TERMUX_PREFIX -# define TERMUX_PREFIX "/data/data/com.termux/files/usr" -#endif - -#ifdef __aarch64__ -# define EM_NATIVE EM_AARCH64 -#elif defined(__arm__) || defined(__thumb__) -# define EM_NATIVE EM_ARM -#elif defined(__x86_64__) -# define EM_NATIVE EM_X86_64 -#elif defined(__i386__) -# define EM_NATIVE EM_386 -#else -# error "unknown arch" -#endif - -#define starts_with(value, str) !strncmp(value, str, sizeof(str) - 1) - -static const char* termux_rewrite_executable(const char* filename, char* buffer, int buffer_len) -{ - if (starts_with(filename, TERMUX_BASE_DIR) || - starts_with(filename, "/system/")) - return filename; - - strcpy(buffer, TERMUX_PREFIX "/bin/"); - char* bin_match = strstr(filename, "/bin/"); - if (bin_match == filename || bin_match == (filename + 4)) { - // We have either found "/bin/" at the start of the string or at - // "/xxx/bin/". Take the path after that. - strncpy(buffer + sizeof(TERMUX_PREFIX "/bin/") - 1, bin_match + 5, buffer_len - sizeof(TERMUX_PREFIX "/bin/")); - filename = buffer; - } - return filename; -} - -static char*const * remove_ld_preload(char*const * envp) -{ - for (int i = 0; envp[i] != NULL; i++) { - if (strstr(envp[i], "LD_PRELOAD=") == envp[i]) { - int env_length = 0; - while (envp[env_length] != NULL) env_length++; - - char** new_envp = malloc(sizeof(char*) * env_length); - int new_envp_idx = 0; - int old_envp_idx = 0; - while (old_envp_idx < env_length) { - if (old_envp_idx != i) { - new_envp[new_envp_idx++] = envp[old_envp_idx]; - } - old_envp_idx++; - } - new_envp[env_length] = NULL; - return new_envp; - } - } - return envp; -} - -int execve(const char* filename, char* const* argv, char* const* envp) -{ - bool android_10_debug = getenv("TERMUX_ANDROID10_DEBUG") != NULL; - if (android_10_debug) { - printf("execve(%s):\n", filename); - int tmp_argv_count = 0; - while (argv[tmp_argv_count] != NULL) { - printf(" %s\n", argv[tmp_argv_count]); - tmp_argv_count++; - } - } - - int fd = -1; - const char** new_argv = NULL; - const char** new_envp = NULL; - - char filename_buffer[512]; - filename = termux_rewrite_executable(filename, filename_buffer, sizeof(filename_buffer)); - - // Error out if the file is not executable: - if (access(filename, X_OK) != 0) goto final; - - fd = open(filename, O_RDONLY); - if (fd == -1) goto final; - - // LD_LIBRARY_PATH messes up system programs with CANNOT_LINK_EXECUTABLE errors. - // If we remove.it, this problem is solved. - // /system/bin/sh is fine, it only uses libc++, libc, and libdl. - if (starts_with(filename, "/system/") && strcmp(filename, "/system/bin/sh") != 0) { - - size_t envp_count = 0; - while (envp[envp_count] != NULL) - envp_count++; - - new_envp = malloc((envp_count + 1) * sizeof(char*)); - - size_t pos = 0; - for (size_t i = 0; i < envp_count; i++) { - // Skip it if it is LD_LIBRARY_PATH or LD_PRELOAD - if (!starts_with(envp[i], "LD_LIBRARY_PATH=") && - !starts_with(envp[i], "LD_PRELOAD=")) - new_envp[pos++] = (const char*)envp[i]; - } - new_envp[pos] = NULL; - - envp = (char**)new_envp; - // Not.sure if needed. - environ = (char**)new_envp; - } - - // execve(2): "A maximum line length of 127 characters is allowed - // for the first line in a #! executable shell script." - char header[128]; - ssize_t read_bytes = read(fd, header, sizeof(header) - 1); - - // If we are executing a non-native ELF file, unset LD_PRELOAD. - // This avoids CANNOT LINK EXECUTABLE errors when running 32-bit code - // on 64-bit. - if (read_bytes >= 20 && !memcmp(header, ELFMAG, SELFMAG)) { - Elf32_Ehdr* ehdr = (Elf32_Ehdr*)header; - if (ehdr->e_machine != EM_NATIVE) { - envp = remove_ld_preload(envp); - } - goto final; - } - if (read_bytes < 5 || !(header[0] == '#' && header[1] == '!')) goto final; - - header[read_bytes] = 0; - char* newline_location = strchr(header, '\n'); - if (newline_location == NULL) goto final; - - // Strip whitespace at end of shebang: - while (*(newline_location - 1) == ' ') newline_location--; - - // Null-terminate the shebang line: - *newline_location = 0; - - // Skip whitespace to find interpreter start: - char* interpreter = header + 2; - while (*interpreter == ' ') interpreter++; - if (interpreter == newline_location) goto final; - - char* arg = NULL; - char* whitespace_pos = strchr(interpreter, ' '); - if (whitespace_pos != NULL) { - // Null-terminate the interpreter string. - *whitespace_pos = 0; - - // Find start of argument: - arg = whitespace_pos + 1; - while (*arg != 0 && *arg == ' ') arg++; - if (arg == newline_location) { - // Only whitespace after interpreter. - arg = NULL; - } - } - - char interp_buf[512]; - const char* new_interpreter = termux_rewrite_executable(interpreter, interp_buf, sizeof(interp_buf)); - if (new_interpreter == interpreter) goto final; - - int orig_argv_count = 0; - while (argv[orig_argv_count] != NULL) orig_argv_count++; - - new_argv = malloc(sizeof(char*) * (4 + orig_argv_count)); - - int current_argc = 0; - new_argv[current_argc++] = basename(interpreter); - if (arg) new_argv[current_argc++] = arg; - new_argv[current_argc++] = filename; - int i = 1; - while (orig_argv_count-- > 1) new_argv[current_argc++] = argv[i++]; - new_argv[current_argc] = NULL; - - filename = new_interpreter; - argv = (char**) new_argv; - -final: - if (fd != -1) close(fd); - int (*real_execve)(const char*, char* const[], char* const[]) = dlsym(RTLD_NEXT, "execve"); - - bool android_10_wrapping = getenv("TERMUX_ANDROID10") != NULL; - if (android_10_wrapping) { - char realpath_buffer[PATH_MAX]; - bool realpath_ok = realpath(filename, realpath_buffer) != NULL; - if (realpath_ok) { - bool wrap_in_proot = (strstr(realpath_buffer, TERMUX_BASE_DIR) != NULL); - if (android_10_debug) { - printf("termux-exec: realpath(\"%s\") = \"%s\", wrapping=%s\n", filename, realpath_buffer, wrap_in_proot ? "yes" : "no"); - } - if (wrap_in_proot) { - orig_argv_count = 0; - while (argv[orig_argv_count] != NULL) orig_argv_count++; - - new_argv = malloc(sizeof(char*) * (2 + orig_argv_count)); - filename = TERMUX_PREFIX "/bin/proot"; - new_argv[0] = "proot"; - for (int i = 0; i < orig_argv_count; i++) { - new_argv[i + 1] = argv[i]; - } - new_argv[orig_argv_count + 1] = NULL; - argv = (char**) new_argv; - // Remove LD_PRELOAD environment variable when wrapping in proot - envp = remove_ld_preload(envp); - } - } else { - errno = 0; - } - - if (android_10_debug) { - printf("real_execve(%s):\n", filename); - int tmp_argv_count = 0; - while (argv[tmp_argv_count] != NULL) { - printf(" %s\n", argv[tmp_argv_count]); - tmp_argv_count++; - } - } - } - - int ret = real_execve(filename, argv, envp); - free(new_argv); - free(new_envp); - return ret; -} diff --git a/termux-exec-debug.json b/termux-exec-debug.json deleted file mode 100644 index 4b45a7d..0000000 --- a/termux-exec-debug.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "control": { - "Package": "termux-exec", - "Version": "9:9.8", - "Architecture": "aarch64", - "Maintainer": "Termux developers", - "Description": "Development version of termux-exec" - }, - - "installation_prefix": "/data/data/com.termux/files/usr", - - "data_files": { - "lib/libtermux-exec.so": { - "source": "libtermux-exec.so" - } - } -} From db738a1144d65cb16caa0900f9f6d2f1895ce96b Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 22 Mar 2025 01:36:34 +0500 Subject: [PATCH 17/47] Added: Re-add `system_linker_exec` support initially added in 2fe47750 and removed in 1fac1073 and `libtermux-exec-linker-ld-preload.so` `$LD_PRELOAD` library variant - `lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c` from `libtermux-exec_nos_c_tre` handles the `system_linker_exec` now and uses `lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c` to get whether `system_linker_exec` should be used based on the `isSystemLinkerExecEnabled()` and `shouldEnableSystemLinkerExecForFile()` functions. - The `libtermux-exec-linker-ld-preload.so` `$LD_PRELOAD` library variant has been added and is meant to intercept additional functions to solve other issues specific to `system_linker_exec` execution. The `libtermux-exec-direct-ld-preload.so` variant needs to support `system_linker_exec` as well as during package updates, before the `linker` variant is set as primary variant, the `direct` variant will get used for certain commands as it gets installed as the primary variant by default, and commands will fail if `system_linker_exec` is required to bypass execution restrictions. The primary `$LD_PRELOAD` library variant to be used is set by copying it to `$TERMUX__PREFIX/usr/lib/libtermux-exec-ld-preload.so` and this path is exported in `$LD_PRELOAD` by `login` script. This is done by the `postinst` script run during package installation, which runs `termux-exec-ld-preload-lib setup` to set the correct variant as per the execution type required for the Termux environment of the host device by running `termux-exec-system-linker-exec is-enabled` to check if `system_linker_exec` is to be enabled. - Added the `string` `TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE` environment variable for whether to use `system_linker_exec` if `TERMUX_EXEC__EXECVE_CALL__INTERCEPT` is enabled. If set to `disable`, `system_linker_exec` will be disabled. If set to `enable`, then `system_linker_exec` will be enabled but only if required. If set to `force`, then `system_linker_exec` will be force enabled even if not required and is supported. The default value is `enable`. Check `shouldEnableSystemLinkerExecForFile()` in `ExecIntercept.h` and `ExecIntercept.c` for more info and how its handled. Docs will be added in a later commit. The `system_linker_exec` will now engage for executable or interpreter paths that are under `TERMUX_APP__DATA_DIR` or `TERMUX_APP__LEGACY_DATA_DIR` instead of `TERMUX__ROOTFS` (`TERMUX_BASE_DIR`). - Use `android_buildVersionSdk_get()` from `libtermux-core_nos_c_tre` library to read `ANDROID__BUILD_VERSION_SDK` environment variable exported by Termux app to get Android build version sdk, and if its not set, then from the `android_get_device_api_level()` call provided by ``, which gets it from the system properties, which should be slower. - Fix the environment `envp` being copied twice during `execve` for system bin paths, once for unsetting `LD_` variables and then to set `TERMUX_EXEC__PROC_SELF_EXE`. The `modifyExecEnv()` function now handles all changes to environment with a single copy. - Fix `TERMUX_EXEC__PROC_SELF_EXE` being set even if `system_linker_exec` is not being used on `targetSdkVersion <= 28`, etc, and also not being unset when going from `system_linker_exec` to direct execution like system binaries. Packages will be patched to detect if `system_linker_exec` is being used to modify their behaviour, which shouldn't be modified for direct execution. --- Makefile | 44 ++- .../ld_preload/termux-exec-ld-preload-lib.in | 203 ++++++++++++++ .../termux-exec-system-linker-exec.in | 251 ++++++++++++++++++ .../TermuxExecLinkerLDPreloadEntryPoint.c | 96 +++++++ .../ld_preload/TermuxExecLDPreload.h | 92 +++++++ .../ld_preload/direct/exec/ExecIntercept.h | 7 +- .../termux_exec/TermuxExecShellEnvironment.h | 61 +++++ .../ld_preload/TermuxExecLDPreload.c | 165 ++++++++++++ .../ld_preload/direct/exec/ExecIntercept.c | 51 +++- .../termux_exec/TermuxExecShellEnvironment.c | 15 ++ packaging/debian/postinst.in | 21 ++ 11 files changed, 999 insertions(+), 7 deletions(-) create mode 100644 app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in create mode 100644 app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in create mode 100644 app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c create mode 100644 lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h create mode 100644 lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c create mode 100644 packaging/debian/postinst.in diff --git a/Makefile b/Makefile index aee3d95..4489d18 100644 --- a/Makefile +++ b/Makefile @@ -63,6 +63,7 @@ export override BUILD_OUTPUT_DIR := $(BUILD_DIR)/output# Default value: `build/o export override TMP_BUILD_OUTPUT_DIR := $(BUILD_OUTPUT_DIR)/tmp# Default value: `build/output/tmp` export override PREFIX_BUILD_OUTPUT_DIR := $(BUILD_OUTPUT_DIR)/usr# Default value: `build/output/usr` +export override BIN_BUILD_OUTPUT_DIR := $(PREFIX_BUILD_OUTPUT_DIR)/bin# Default value: `build/output/usr/bin` export override LIB_BUILD_OUTPUT_DIR := $(PREFIX_BUILD_OUTPUT_DIR)/lib# Default value: `build/output/usr/lib` export override LIBEXEC_BUILD_OUTPUT_DIR := $(PREFIX_BUILD_OUTPUT_DIR)/libexec# Default value: `build/output/usr/libexec` export override TESTS_BUILD_OUTPUT_DIR := $(LIBEXEC_BUILD_OUTPUT_DIR)/installed-tests/termux-exec# Default value: `build/output/usr/libexec/installed-tests/termux-exec` @@ -236,7 +237,7 @@ CLANG_TIDY ?= clang-tidy # - https://www.gnu.org/software/make/manual/html_node/Parallel-Disable.html .NOTPARALLEL: -all: | pre-build build-libtermux-exec_nos_c_tre build-libtermux-exec-direct-ld-preload build-termux-exec-main-app +all: | pre-build build-libtermux-exec_nos_c_tre build-libtermux-exec-direct-ld-preload build-libtermux-exec-linker-ld-preload build-termux-exec-main-app @printf "\ntermux-exec-package: %s\n" "Building packaging/debian/*" @mkdir -p $(DEBIAN_PACKAGING_BUILD_OUTPUT_DIR) find packaging/debian -mindepth 1 -maxdepth 1 -type f -name "*.in" -exec sh -c \ @@ -258,6 +259,15 @@ pre-build: | clean build-termux-exec-main-app: @printf "\ntermux-exec-package: %s\n" "Building app/main" + @mkdir -p $(BIN_BUILD_OUTPUT_DIR) + + + @printf "\ntermux-exec-package: %s\n" "Building app/main/scripts/*" + find app/main/scripts -type f -name "*.in" -exec sh -c \ + 'sed $(TERMUX__CONSTANTS__SED_ARGS) "$$1" > $(BIN_BUILD_OUTPUT_DIR)/"$$(basename "$$1" | sed "s/\.in$$//")"' sh "{}" \; + find $(BIN_BUILD_OUTPUT_DIR) -maxdepth 1 -exec chmod 700 "{}" \; + find app/main/scripts -type l -exec cp -a "{}" $(BIN_BUILD_OUTPUT_DIR)/ \; + build-libtermux-exec_nos_c_tre: @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre" @@ -307,6 +317,10 @@ build-libtermux-exec-direct-ld-preload: @# primary library variant exported in `$LD_PRELOAD` by copying it @# to `libtermux-exec-ld-preload.so`. @# Creating a symlink may have performance impacts. + @# The `postinst` script run during package installation runs + @# `termux-exec-ld-preload-lib setup` to set the correct variant + @# as per the execution type required for the Termux environment + @# of the host device by running. cp -a $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec-direct-ld-preload.so $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec-ld-preload.so @# For backward compatibility, symlink `libtermux-exec.so` to @@ -317,6 +331,24 @@ build-libtermux-exec-direct-ld-preload: rm -f $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec.so ln -s libtermux-exec-ld-preload.so $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec.so +build-libtermux-exec-linker-ld-preload: + @mkdir -p $(LIB_BUILD_OUTPUT_DIR) + + @# Unlike `libtermux-exec_nos_c_tre.so` and `libtermux-exec_nos_c_tre.a`, all + @# symbols are hidden, except the exported functions with + @# `default` visibility with `__attribute__((visibility("default")))`, + @# defined in the `TermuxExecLinkerLDPreloadEntryPoint.c` file meant to + @# be intercepted by `$LD_PRELOAD`. + @# `nm --demangle --dynamic --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/lib/libtermux-exec-linker-ld-preload.so` + @printf "\ntermux-exec-package: %s\n" "Building lib/libtermux-exec-linker-ld-preload" + $(CC) $(CFLAGS) $(LIBTERMUX_EXEC__NOS__C__CPPFLAGS) \ + -L$(LIB_BUILD_OUTPUT_DIR) $(LDFLAGS) -Wl,--exclude-libs=ALL \ + $(TERMUX__CONSTANTS__MACRO_FLAGS) \ + -fPIC -shared -fvisibility=hidden \ + -o $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec-linker-ld-preload.so \ + app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c \ + -l:libtermux-exec_nos_c_tre.a -l:libtermux-core_nos_c_tre.a + clean: @@ -324,11 +356,14 @@ clean: install: @printf "termux-exec-package: %s\n" "Installing termux-exec-package in $(TERMUX_EXEC_PKG__INSTALL_PREFIX)" + install -d $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/bin install -d $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include install -d $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib install -d $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/libexec + find $(BIN_BUILD_OUTPUT_DIR) -maxdepth 1 \( -type f -o -type l \) -exec cp -a "{}" $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/bin/ \; + rm -rf $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec install -d $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec/termux @@ -338,6 +373,7 @@ install: install $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec-ld-preload.so $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec-ld-preload.so install $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec-direct-ld-preload.so $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec-direct-ld-preload.so + install $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec-linker-ld-preload.so $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec-linker-ld-preload.so @# Use `cp` for symlink as `install` will copy the target regular file instead. cp -a $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec.so $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec.so @@ -350,6 +386,9 @@ install: uninstall: @printf "termux-exec-package: %s\n" "Uninstalling termux-exec-package from $(TERMUX_EXEC_PKG__INSTALL_PREFIX)" + find app/main/scripts \( -type f -o -type l \) -exec sh -c \ + 'rm -f $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/bin/"$$(basename "$$1" | sed "s/\.in$$//")"' sh "{}" \; + rm -rf $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec @@ -358,6 +397,7 @@ uninstall: rm -f $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec-ld-preload.so rm -f $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec-direct-ld-preload.so + rm -f $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec-linker-ld-preload.so rm -f $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec.so @printf "\ntermux-exec-package: %s\n\n" "Uninstall termux-exec-package successful" @@ -382,4 +422,4 @@ check: -.PHONY: all pre-build build-termux-exec-main-app build-libtermux-exec_nos_c_tre build-libtermux-exec-direct-ld-preload clean install uninstall packaging-debian-build format check +.PHONY: all pre-build build-termux-exec-main-app build-libtermux-exec_nos_c_tre build-libtermux-exec-direct-ld-preload build-libtermux-exec-linker-ld-preload clean install uninstall packaging-debian-build format check diff --git a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in new file mode 100644 index 0000000..ec19976 --- /dev/null +++ b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in @@ -0,0 +1,203 @@ +#!@TERMUX__PREFIX@/bin/sh +# shellcheck shell=sh +# shellcheck disable=SC2039,SC2059,SC3043 + +termux_exec__ld_preload_lib__init() { + +TERMUX_EXEC__LD_PRELOAD_LIB__LOG_LEVEL___N="@TERMUX_ENV__S_TERMUX_EXEC@LD_PRELOAD_LIB__LOG_LEVEL" +termux_exec__ld_preload_lib__copy_variable TERMUX_EXEC__LD_PRELOAD_LIB__LOG_LEVEL "$TERMUX_EXEC__LD_PRELOAD_LIB__LOG_LEVEL___N" || return $? +case "${TERMUX_EXEC__LD_PRELOAD_LIB__LOG_LEVEL:-}" in + 0|1|2) :;; *) TERMUX_EXEC__LD_PRELOAD_LIB__LOG_LEVEL=1;; # Default: `1` (OFF=0, NORMAL=1, DEBUG=2) +esac + +TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL___N="@TERMUX_ENV__S_TERMUX_EXEC@SYSTEM_LINKER_EXEC__LOG_LEVEL" + +TERMUX__PREFIX___N="@TERMUX_ENV__S_TERMUX@PREFIX" +termux_exec__ld_preload_lib__copy_variable TERMUX__PREFIX "$TERMUX__PREFIX___N" || return $? +case "${TERMUX__PREFIX:-}" in + /*[!/]) :;; *) TERMUX__PREFIX="@TERMUX__PREFIX@";; +esac + +} + + + +termux_exec__ld_preload_lib__log() { local log_level="${1}"; shift; if [ "$TERMUX_EXEC__LD_PRELOAD_LIB__LOG_LEVEL" -ge "$log_level" ]; then echo "@TERMUX__LNAME@-exec:" "$@"; fi } +termux_exec__ld_preload_lib__log_error() { echo "@TERMUX__LNAME@-exec:" "$@" 1>&2; } +termux_exec__ld_preload_lib__log_error_for_level() { local log_level="${1}"; shift; if [ "$TERMUX_EXEC__LD_PRELOAD_LIB__LOG_LEVEL" -ge "$log_level" ]; then echo "@TERMUX__LNAME@-exec:" "$@" 1>&2; fi } + + + +## +# `termux_exec__ld_preload_lib__main` [``] +## +termux_exec__ld_preload_lib__main() { + + termux_exec__ld_preload_lib__init || return $? + + + if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then + termux_exec__ld_preload_lib__show_help || return $? + return 0 + elif [ "${1:-}" = "--version" ]; then + echo "@TERMUX_EXEC_PKG__VERSION@" || return $? + return 0 + fi + + + local command_type="${1:-}" + [ $# -gt 0 ] && shift 1 + + if [ "$command_type" = "setup" ]; then + if [ "${1:-}" = "-q" ] || [ "${1:-}" = "--quiet" ]; then + TERMUX_EXEC__LD_PRELOAD_LIB__LOG_LEVEL=0 + shift 1 + elif [ "${1:-}" = "-v" ]; then + TERMUX_EXEC__LD_PRELOAD_LIB__LOG_LEVEL=2 + shift 1 + fi + else + echo "The command '$command_type' passed to 'termux-exec-ld-preload-lib' is not valid." 1>&2 + return 64 # EX__USAGE + fi + + + termux_exec__ld_preload_lib__set_variable "$TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL___N" "$TERMUX_EXEC__LD_PRELOAD_LIB__LOG_LEVEL" || return $? + export "${TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL___N?}" || return $? + + + if [ "$command_type" = "setup" ]; then + termux_exec__ld_preload_lib__setup__run_command setup || return $? + fi + + return 0 + +} + + + +## +# `termux_exec__ld_preload_lib__setup__run_command` +## +termux_exec__ld_preload_lib__setup__run_command() { + + local system_linker_exec_enabled + + system_linker_exec_enabled="$(termux-exec-system-linker-exec is-enabled)" || return $? + + local ld_preload_file + + if [ "$system_linker_exec_enabled" != "true" ]; then + ld_preload_file="$TERMUX__PREFIX/lib/libtermux-exec-direct-ld-preload.so" + else + ld_preload_file="$TERMUX__PREFIX/lib/libtermux-exec-linker-ld-preload.so" + fi + termux_exec__ld_preload_lib__log 1 "Setting primary Termux '\$LD_PRELOAD' library in 'libtermux-exec-ld-preload.so' to '$ld_preload_file'" + + mkdir -p "$TERMUX__PREFIX/lib" || return $? + + # Prevent `Segmentation fault (SIGSEGV fault addr)` when running + # commands in calling shell of the script after new file has been + # copied below. + rm -f "$TERMUX__PREFIX/lib/libtermux-exec-ld-preload.so" || return $? + + # Prevent `CANNOT LINK EXECUTABLE "cp": library "/data/data/com.termux/files/usr/lib/libtermux-exec-ld-preload.so" not found: needed by main executable` + unset LD_PRELOAD || return $? + + cp -a "$ld_preload_file" "$TERMUX__PREFIX/lib/libtermux-exec-ld-preload.so" || return $? + +} + + + +## +# `termux_exec__ld_preload_lib__is_valid_shell_name` `` +## +termux_exec__ld_preload_lib__is_valid_shell_name() { + + if [ -z "${1:-}" ]; then + return 1 + fi + + local name_rest="${1#?}" # 1:end + local name_first_char="${1%"$name_rest"}" # 0:1 + case "$name_first_char" in + [a-zA-Z_]) + case "$name_rest" in + *[!a-zA-Z0-9_]*) return 1;; + *) return 0;; + esac;; + *) return 1;; + esac + +} + +## +# `termux_exec__ld_preload_lib__set_variable` `` `` +## +termux_exec__ld_preload_lib__set_variable() { + + local variable_name="${1:-}" + local variable_value="${2:-}" + + if ! termux_exec__ld_preload_lib__is_valid_shell_name "$variable_name"; then + termux_exec__ld_preload_lib__log_error "The variable_name '$variable_name' is not a valid shell variable name." + return 64 # EX__USAGE + fi + + eval "$variable_name"=\"\$variable_value\" + +} + +termux_exec__ld_preload_lib__copy_variable() { + + local output_variable_name="${1:-}" + local input_variable_name="${2:-}" + + if ! termux_exec__ld_preload_lib__is_valid_shell_name "$output_variable_name"; then + termux_exec__ld_preload_lib__log_error "The output_variable_name '$output_variable_name' is not a valid shell variable name." + return 1 + fi + + if ! termux_exec__ld_preload_lib__is_valid_shell_name "$input_variable_name"; then + termux_exec__ld_preload_lib__log_error "The input_variable_name '$input_variable_name' is not a valid shell variable name." + return 1 + fi + + eval "$output_variable_name"=\"\$\{"$input_variable_name":-\}\" + +} + + + +## +# `termux_exec__ld_preload_lib__show_help` +## +termux_exec__ld_preload_lib__show_help() { + + cat <<'HELP_EOF' +termux-exec-ld-preload-lib can be used to manage Termux '$LD_PRELOAD' +library. + + +Usage: + termux-exec-ld-preload-lib [command_options] + +Available commands: + setup Setup the primary Termux '$LD_PRELOAD' + library in 'libtermux-exec-ld-preload.so' + to direct or linker variant. + +Available command_options: + [ -h | --help ] Display this help screen. + [ --version ] Display version. + [ -q | --quiet ] Set log level to 'OFF'. + [ -v ] + Set log level to 'DEBUG'. +HELP_EOF + +} + + + +termux_exec__ld_preload_lib__main "$@" diff --git a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in new file mode 100644 index 0000000..e1f0c52 --- /dev/null +++ b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in @@ -0,0 +1,251 @@ +#!@TERMUX__PREFIX@/bin/sh +# shellcheck shell=sh +# shellcheck disable=SC2039,SC2059,SC3043 + +termux_exec__system_linker_exec__init() { + +TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL___N="@TERMUX_ENV__S_TERMUX_EXEC@SYSTEM_LINKER_EXEC__LOG_LEVEL" +termux_exec__system_linker_exec__copy_variable TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL "$TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL___N" || return $? +case "${TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL:-}" in + 0|1|2) :;; *) TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL=1;; # Default: `1` (OFF=0, NORMAL=1, DEBUG=2) +esac + +} + + + +termux_exec__system_linker_exec__log() { local log_level="${1}"; shift; if [ "$TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL" -ge "$log_level" ]; then echo "@TERMUX__LNAME@-exec:" "$@"; fi } +termux_exec__system_linker_exec__log_error() { echo "@TERMUX__LNAME@-exec:" "$@" 1>&2; } +termux_exec__system_linker_exec__log_error_for_level() { local log_level="${1}"; shift; if [ "$TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL" -ge "$log_level" ]; then echo "@TERMUX__LNAME@-exec:" "$@" 1>&2; fi } + + + +## +# `termux_exec__system_linker_exec__main` [``] +## +termux_exec__system_linker_exec__main() { + + termux_exec__system_linker_exec__init || return $? + + + if [ "${1:-}" = "-h" ] || [ "${1:-}" = "--help" ]; then + termux_exec__system_linker_exec__show_help || return $? + return 0 + elif [ "${1:-}" = "--version" ]; then + echo "@TERMUX_EXEC_PKG__VERSION@" || return $? + return 0 + fi + + + local command_type="${1:-}" + [ $# -gt 0 ] && shift 1 + + if [ "$command_type" = "is-enabled" ]; then + if [ "${1:-}" = "-q" ] || [ "${1:-}" = "--quiet" ]; then + TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL=0 + shift 1 + elif [ "${1:-}" = "-v" ]; then + TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL=2 + shift 1 + fi + else + echo "The command '$command_type' passed to 'termux-exec-system-linker-exec' is not valid." 1>&2 + return 64 # EX__USAGE + fi + + + local output="" + + # TODO: Add `should-enable-for-file` command. + if [ "$command_type" = "is-enabled" ]; then + termux_exec__system_linker_exec__enabled__run_command output || return $? + fi + + if [ -n "$output" ]; then + echo "$output" || return $? + fi + + return 0 + +} + + + +## +# **IMPORTANT** The logic must be kept consistent with the +# `isSystemLinkerExecEnabled()` function in `TermuxExecLDPreload.c`. +# +# +# `termux_exec__system_linker_exec__enabled__run_command` `` +## +termux_exec__system_linker_exec__enabled__run_command() { + + local return_value + + local output_variable_name="${1:-}" + + termux_exec__system_linker_exec__set_variable "$output_variable_name" "" || return $? + + local android_build_version_sdk="${ANDROID__BUILD_VERSION_SDK:-}" + case "$android_build_version_sdk" in + ''|*[!0-9]*) + android_build_version_sdk="$(getprop "ro.build.version.sdk")" || true + case "$android_build_version_sdk" in + ''|*[!0-9]*) + termux_exec__system_linker_exec__log_error "Failed to get android_build_version_sdk value from 'getprop': '$android_build_version_sdk'" + return 1 + ;; + esac + ;; + esac + termux_exec__system_linker_exec__log_error_for_level 2 "android_build_version_sdk: '$android_build_version_sdk'" + + + local system_linker_exec_mode="${TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE:-}" + case "${system_linker_exec_mode:-}" in + disable|enable|force) :;; + *) system_linker_exec_mode="enable";; + esac + termux_exec__system_linker_exec__log_error_for_level 2 "system_linker_exec_mode: '$system_linker_exec_mode'" + + + local __system_linker_exec_enabled="false" + if [ "$system_linker_exec_mode" = "disable" ]; then + __system_linker_exec_enabled="false" + + elif [ "$system_linker_exec_mode" = "force" ]; then + local system_linker_exec_available="false" + # If running on Android `>= 10`. + if [ "$android_build_version_sdk" -ge 29 ]; then + system_linker_exec_available="true" + fi + termux_exec__system_linker_exec__log_error_for_level 2 "system_linker_exec_available: '$system_linker_exec_available'" + + if [ "$system_linker_exec_available" = "true" ]; then + __system_linker_exec_enabled="true" + fi + + else + # If running on Android `>= 10`. + if [ "$android_build_version_sdk" -ge 29 ]; then + local se_process_context + + return_value=0 + se_process_context="$(cat "/proc/self/attr/current")" || return_value=$? + if [ "$return_value" -ne 0 ] || [ -z "$se_process_context" ]; then + termux_exec__system_linker_exec__log_error "Failed to get se_process_context value from '/proc/self/attr/current': '$se_process_context'" + return 1 + fi + termux_exec__system_linker_exec__log_error_for_level 2 "se_process_context_from_file: '$se_process_context'" + + local app_data_file_exec_exempted="false" + case "$se_process_context" in + "u:r:untrusted_app_25:"*|"u:r:untrusted_app_27:"*) + app_data_file_exec_exempted="true" + ;; + esac + termux_exec__system_linker_exec__log_error_for_level 2 "app_data_file_exec_exempted: '$app_data_file_exec_exempted'" + + if [ "$app_data_file_exec_exempted" = "false" ]; then + __system_linker_exec_enabled="true" + fi + fi + fi + termux_exec__system_linker_exec__log_error_for_level 2 "system_linker_exec_enabled: '$__system_linker_exec_enabled'" + + termux_exec__system_linker_exec__set_variable "$output_variable_name" "$__system_linker_exec_enabled" + +} + + + +## +# `termux_exec__system_linker_exec__is_valid_shell_name` `` +## +termux_exec__system_linker_exec__is_valid_shell_name() { + + if [ -z "${1:-}" ]; then + return 1 + fi + + local name_rest="${1#?}" # 1:end + local name_first_char="${1%"$name_rest"}" # 0:1 + case "$name_first_char" in + [a-zA-Z_]) + case "$name_rest" in + *[!a-zA-Z0-9_]*) return 1;; + *) return 0;; + esac;; + *) return 1;; + esac + +} + +## +# `termux_exec__system_linker_exec__set_variable` `` `` +## +termux_exec__system_linker_exec__set_variable() { + + local variable_name="${1:-}" + local variable_value="${2:-}" + + if ! termux_exec__system_linker_exec__is_valid_shell_name "$variable_name"; then + termux_exec__system_linker_exec__log_error "The variable_name '$variable_name' is not a valid shell variable name." + return 64 # EX__USAGE + fi + + eval "$variable_name"=\"\$variable_value\" + +} + +termux_exec__system_linker_exec__copy_variable() { + + local output_variable_name="${1:-}" + local input_variable_name="${2:-}" + + if ! termux_exec__system_linker_exec__is_valid_shell_name "$output_variable_name"; then + termux_exec__system_linker_exec__log_error "The output_variable_name '$output_variable_name' is not a valid shell variable name." + return 1 + fi + + if ! termux_exec__system_linker_exec__is_valid_shell_name "$input_variable_name"; then + termux_exec__system_linker_exec__log_error "The input_variable_name '$input_variable_name' is not a valid shell variable name." + return 1 + fi + + eval "$output_variable_name"=\"\$\{"$input_variable_name":-\}\" + +} + + + +## +# `termux_exec__system_linker_exec__show_help` +## +termux_exec__system_linker_exec__show_help() { + + cat <<'HELP_EOF' +termux-exec-system-linker-exec can be used to get states of +'system_linker_exec' in Termux. + + +Usage: + termux-exec-system-linker-exec [command_options] + +Available commands: + is-enabled Get whether 'system_linker_exec' + is enabled in Termux. + +Available command_options: + [ -h | --help ] Display this help screen. + [ --version ] Display version. + [ -q | --quiet ] Set log level to 'OFF'. + [ -v ] + Set log level to 'DEBUG'. +HELP_EOF + +} + + + +termux_exec__system_linker_exec__main "$@" diff --git a/app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c b/app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c new file mode 100644 index 0000000..1a90e7d --- /dev/null +++ b/app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c @@ -0,0 +1,96 @@ +#define _GNU_SOURCE +#include +#include +#include + +#include +#include +#include + +/** + * This file defines functions intercepted by `libtermux-exec-linker-ld-preload.so` using `$LD_PRELOAD`. + * + * All exported functions must explicitly enable `default` visibility + * with `__attribute__((visibility("default")))` as `libtermux-exec-linker-ld-preload.so` + * is compiled with `-fvisibility=hidden` so that no other internal + * functions are exported. + * + * You can check exported symbols for dynamic linking after building with: + * `nm --demangle --dynamic --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/lib/libtermux-exec-linker-ld-preload.so`. + */ + + + +void termuxExec_linkerLdPreload_initProcess() { + termuxExec_process_initProcess(TERMUX_EXEC_PKG__VERSION "+linker-ld-preload", NULL); +} + + + +__attribute__((visibility("default"))) +int execl(const char *name, const char *arg, ...) { + termuxExec_linkerLdPreload_initProcess(); + + va_list ap; + va_start(ap, arg); + int result = execlIntercept(true, ExecL, name, arg, ap); + va_end(ap); + return result; +} + +__attribute__((visibility("default"))) +int execlp(const char *name, const char *arg, ...) { + termuxExec_linkerLdPreload_initProcess(); + + va_list ap; + va_start(ap, arg); + int result = execlIntercept(true, ExecLP, name, arg, ap); + va_end(ap); + return result; +} + +__attribute__((visibility("default"))) +int execle(const char *name, const char *arg, ...) { + termuxExec_linkerLdPreload_initProcess(); + + va_list ap; + va_start(ap, arg); + int result = execlIntercept(true, ExecLE, name, arg, ap); + va_end(ap); + return result; +} + +__attribute__((visibility("default"))) +int execv(const char *name, char *const *argv) { + termuxExec_linkerLdPreload_initProcess(); + + return execvIntercept(true, name, argv); +} + +__attribute__((visibility("default"))) +int execvp(const char *name, char *const *argv) { + termuxExec_linkerLdPreload_initProcess(); + + return execvpIntercept(true, name, argv); +} + +__attribute__((visibility("default"))) +int execvpe(const char *name, char *const *argv, char *const *envp) { + termuxExec_linkerLdPreload_initProcess(); + + return execvpeIntercept(true, name, argv, envp); +} + +__attribute__((visibility("default"))) +int fexecve(int fd, char *const *argv, char *const *envp) { + termuxExec_linkerLdPreload_initProcess(); + + return fexecveIntercept(true, fd, argv, envp); +} + +__attribute__((visibility("default"))) +int execve(const char *name, char *const argv[], char *const envp[]) { + termuxExec_linkerLdPreload_initProcess(); + + return execveIntercept(true, name, argv, envp); +} diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h new file mode 100644 index 0000000..063fe90 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h @@ -0,0 +1,92 @@ +#ifndef LIBTERMUX_EXEC__NOS__C__TERMUX_EXEC_LD_PRELOAD___H +#define LIBTERMUX_EXEC__NOS__C__TERMUX_EXEC_LD_PRELOAD___H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + + +/** The path to android system linker. */ +#if UINTPTR_MAX == 0xffffffff +#define SYSTEM_LINKER_PATH "/system/bin/linker"; +#elif UINTPTR_MAX == 0xffffffffffffffff +#define SYSTEM_LINKER_PATH "/system/bin/linker64"; +#endif + + +/** + * Whether usage of `system_linker_exec` is to be enabled, like to + * bypass app data file execute restrictions. + * + * A call is made to `termuxExec_systemLinkerExec_mode_get()` to + * get the config for using `system_linker_exec`. + * + * If `disable` is set, then `system_linker_exec` should not be used + * and the default `direct` execution type should be used. + * + * If `enable` is set, then `system_linker_exec` should only be used if: + * - `system_linker_exec` is required to bypass app data file execute + * restrictions, i.e device is running on Android `>= 10`. + * - Effective user does not equal root (`0`) and shell (`2000`) user (used for + * [`adb`](https://developer.android.com/tools/adb)). + * - `TERMUX__SE_PROCESS_CONTEXT` or its fallback `/proc/self/attr/current` + * does not start with `PROCESS_CONTEXT_PREFIX__UNTRUSTED_APP_25` and + * `PROCESS_CONTEXT_PREFIX__UNTRUSTED_APP_27` for which restrictions + * are exempted. + * + * If `force` is set, then `system_linker_exec` should only be used if: + * - `system_linker_exec` is supported, i.e device is running on Android `>= 10`. + * This can be used if running in an untrusted app with `targetSdkVersion` `<= 28`. + * + * See also `shouldEnableSystemLinkerExecForFile()`. + * + * **IMPORTANT** The logic must be kept consistent with the + * `termux_exec__system_linker_exec__enabled__run_command()` function + * in `termux-exec-system-linker-exec`. + * + * @return Returns `0` if `system_linker_exec` is to be enabled, `1` if + * `system_linker_exec` should not be used, otherwise `-1` on failures. + */ +int isSystemLinkerExecEnabled(); + +/** + * Whether to use `system_linker_exec` for an executable file, like to + * bypass app data file execute restrictions. + * + * A call is made to `isSystemLinkerExecEnabled()` to check if + * `system_linker_exec` is to be enabled. If its enabled, then + * `system_linker_exec` is only to be used if + * `isPathUnderTermuxAppDataDir()` returns `true` for the + * `executablePath`. + * + * The executable or interpreter paths are checked under + * `TERMUX_APP__DATA_DIR` or `TERMUX_APP__LEGACY_DATA_DIR` instead of + * `TERMUX__ROOTFS` as files could be executed from `TERMUX__APPS_DIR` + * and `TERMUX__CACHE_DIR`, which are not under the Termux rootfs. + * Additionally, Termux rootfs may not exist under app data directory + * at all and could be under another directory under Android rootfs `/`, + * like if compiling packages for `shell` user for the `com.android.shell` + * package with the Termux rootfs under `/data/local/tmp` instead of + * `/data/data/com.android.shell` (and using `force` mode) or + * compiling packages for `/system` directory. + * + * See also `isSystemLinkerExecEnabled()`. + * + * @param executablePath The **normalized** executable or interpreter + * path that will actually be executed. + * @return Returns `0` if `system_linker_exec` is to be enabled for + * the file, `1` if `system_linker_exec` is not to be enabled, + * otherwise `-1` on failures. + */ +int shouldEnableSystemLinkerExecForFile(const char *executablePath); + + + +#ifdef __cplusplus +} +#endif + +#endif // LIBTERMUX_EXEC__NOS__C__TERMUX_EXEC_LD_PRELOAD___H diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h index 66872b7..f3335d7 100644 --- a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h @@ -246,6 +246,9 @@ int modifyExecEnv(char *const *envp, char ***newEnvpPointer, * `TermuxFileHeaderInfo.origInterpreterPath`, otherwise the original * `argv[0]` passed to `execve()` will be preserved. * + * If `shouldEnableSystemLinkerExec` is `true`, then `argv[1]` will be + * set to `executablePath` to be executed by the linker. + * * If `interpreterSet` is set, then `TermuxFileHeaderInfo.interpreterArg` * will be appended if set, followed by the `origExecutablePath` * passed to `execve()`. @@ -261,13 +264,15 @@ int modifyExecEnv(char *const *envp, char ***newEnvpPointer, * @param interpreterSet Whether a interpreter is set in the executable * file. * @param info The `TermuxFileHeaderInfo` for the executable file. + * @param shouldEnableSystemLinkerExec Whether `system_linker_exec` + * should be used to execute the path. * @return Returns `0` if successfully modified the args, otherwise * `-1` on failures. Its the callers responsibility to call `free()` * on the `newArgvPointer` passed. */ int modifyExecArgs(char *const *argv, const char ***newArgvPointer, const char *origExecutablePath, const char *executablePath, - bool interpreterSet, struct TermuxFileHeaderInfo *info); + bool interpreterSet, bool shouldEnableSystemLinkerExec, struct TermuxFileHeaderInfo *info); diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h index 1b060e8..478dd3c 100644 --- a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h @@ -54,6 +54,53 @@ extern "C" { static const int ENV_DEF_VAL__TERMUX_EXEC__EXECVE_CALL__INTERCEPT = 1; + + + +/** + * Termux environment variables `termux-exec` `system_linker_exec` scope. + * + * Default value: `TERMUX_EXEC__SYSTEM_LINKER_EXEC__` + */ +#define TERMUX_ENV__S_TERMUX_EXEC__SYSTEM_LINKER_EXEC TERMUX_ENV__S_TERMUX_EXEC "SYSTEM_LINKER_EXEC__" + +/** + * Environment variable for whether use System Linker Exec solution, + * like to bypass App Data File Execute restrictions. + * + * See also `shouldEnableSystemLinkerExecForFile()`. + * + * Type: `string` + * Default key: `TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE` + * Default value: ENV_DEF_VAL__TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE + * Values: + * - `disable` (0) - The `system_linker_exec` will be disabled. + * - `enable` (1) - The `system_linker_exec` will be enabled but only if required. + * - `force` (2) - The `system_linker_exec` will be force enabled even if not required. + */ +#define ENV__TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE TERMUX_ENV__S_TERMUX_EXEC__SYSTEM_LINKER_EXEC "MODE" +static const int ENV_DEF_VAL__TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE = 1; + + + +/** + * Environment variable for the path to the executable file is being + * executed by `execve()` is using `system_linker_exec`. + * + * Type: `string` + * Default key: `TERMUX_EXEC__PROC_SELF_EXE` + * Values: + * - The normalized, absolutized and prefixed path for the executable + * file is being executed by `execve()` if `system_linker_exec` is + * being used. + */ +#define ENV__TERMUX_EXEC__PROC_SELF_EXE TERMUX_ENV__S_TERMUX_EXEC "PROC_SELF_EXE" +#define ENV_PREFIX__TERMUX_EXEC__PROC_SELF_EXE ENV__TERMUX_EXEC__PROC_SELF_EXE "=" + + + + + /** * Returns the `termux-exec` config for `Logger` log level * based on the `ENV__TERMUX_EXEC__LOG_LEVEL` env variable. @@ -77,6 +124,20 @@ int termuxExec_execveCall_intercept_get(); + + +/** + * Returns the `termux-exec` config for `system_linker_exec` based on + * the `ENV__TERMUX_EXEC__SYSTEM_LINKER_EXEC` env variable. + * + * @return Return `0` if `ENV__TERMUX_EXEC__SYSTEM_LINKER_EXEC` is set + * to `disable`, `1` if set to `enable`, `2` if set to `force`, + * otherwise defaults to `1` (`enable`). + */ +int termuxExec_systemLinkerExec_mode_get(); + + + #ifdef __cplusplus } #endif diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c new file mode 100644 index 0000000..676c71f --- /dev/null +++ b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c @@ -0,0 +1,165 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static const char* LOG_TAG = "ld-preload"; + +static int sSystemLinkerExecEnabled = -1; + + + +int isSystemLinkerExecEnabled() { + if (sSystemLinkerExecEnabled == 0 || sSystemLinkerExecEnabled == 1) { + return sSystemLinkerExecEnabled; + } + + bool isRunningTests = libtermux_exec__nos__c__getIsRunningTests(); + + int systemLinkerExecMode = termuxExec_systemLinkerExec_mode_get(); + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "system_linker_exec_mode: '%d'", systemLinkerExecMode); + } + + int systemLinkerExecEnabled = 1; + if (systemLinkerExecMode == 0) { // disable + systemLinkerExecEnabled = 1; // disable + + } else if (systemLinkerExecMode == 2) { // force + int androidBuildVersionSdk = android_buildVersionSdk_get(); + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "android_build_version_sdk: '%d'", androidBuildVersionSdk); + } + + bool systemLinkerExecAvailable = false; + // If running on Android `>= 10`. + systemLinkerExecAvailable = androidBuildVersionSdk >= 29; + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "system_linker_exec_available: '%d'", systemLinkerExecAvailable); + } + + if (systemLinkerExecAvailable) { + systemLinkerExecEnabled = 0; // enable + } + + } else { // enable + if (systemLinkerExecMode != 1) { + logErrorDebug(LOG_TAG, "Warning: Ignoring invalid system_linker_exec_mode value and using '1' instead"); + } + + bool appDataFileExecExempted = false; + + int androidBuildVersionSdk = android_buildVersionSdk_get(); + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "android_build_version_sdk: '%d'", androidBuildVersionSdk); + } + + // If running on Android `>= 10`. + if (androidBuildVersionSdk >= 29) { + // If running as root or shell user, then the process will + // be assigned a different process context like + // `PROCESS_CONTEXT__AOSP_SU`, + // `PROCESS_CONTEXT__MAGISK_SU` or + // `PROCESS_CONTEXT__SHELL`, which will not be the same + // as the one that's exported in + // `ENV__TERMUX__SE_PROCESS_CONTEXT`, so we need to check + // effective uid equals `0` or `2000` instead. Moreover, + // other su providers may have different contexts, so we + // cannot just check AOSP or MAGISK contexts. + // - https://man7.org/linux/man-pages/man2/getuid.2.html + uid_t uid = geteuid(); + if (uid == 0 || uid == 2000) { + logErrorVVerbose(LOG_TAG, "uid: '%d'", uid); + appDataFileExecExempted = true; + } else { + char seProcessContext[80]; + bool getSeProcessContextSuccess = false; + + if (getSeProcessContextFromEnv(LOG_TAG, ENV__TERMUX__SE_PROCESS_CONTEXT, + seProcessContext, sizeof(seProcessContext))) { + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "se_process_context_from_env: '%s'", seProcessContext); + } + getSeProcessContextSuccess = true; + } else if (getSeProcessContextFromFile(LOG_TAG, + seProcessContext, sizeof(seProcessContext))) { + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "se_process_context_from_file: '%s'", seProcessContext); + } + getSeProcessContextSuccess = true; + } + + if (getSeProcessContextSuccess) { + appDataFileExecExempted = stringStartsWith(seProcessContext, PROCESS_CONTEXT_PREFIX__UNTRUSTED_APP_25) || + stringStartsWith(seProcessContext, PROCESS_CONTEXT_PREFIX__UNTRUSTED_APP_27); + } + } + + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "app_data_file_exec_exempted: '%d'", appDataFileExecExempted); + } + + if (!appDataFileExecExempted) { + systemLinkerExecEnabled = 0; // enable + } + } + } + + sSystemLinkerExecEnabled = systemLinkerExecEnabled; + + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "system_linker_exec_enabled: '%d'", + sSystemLinkerExecEnabled == 0 ? true : false); + } + + return sSystemLinkerExecEnabled; +} + +int shouldEnableSystemLinkerExecForFile(const char *executablePath) { + int systemLinkerExecResult = isSystemLinkerExecEnabled(); + // If error or disabled, then just return. + if (systemLinkerExecResult != 0) { + return systemLinkerExecResult; + } + + bool isRunningTests = libtermux_exec__nos__c__getIsRunningTests(); + + int isExecutableUnderTermuxAppDataDir = termuxApp_dataDir_isPathUnder(LOG_TAG, + executablePath, NULL, NULL); + if (isExecutableUnderTermuxAppDataDir < 0) { + return -1; + } + + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "is_exe_under_termux_app_data_dir: '%d'", + isExecutableUnderTermuxAppDataDir == 0 ? true : false); + } + + bool shouldEnableSystemLinkerExec = isExecutableUnderTermuxAppDataDir == 0; + + if (!isRunningTests) { + logErrorVVerbose(LOG_TAG, "system_linker_exec_enabled_for_file: '%d'", + shouldEnableSystemLinkerExec); + } + + return shouldEnableSystemLinkerExec ? 0 : 1; +} diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c index ee31875..493315f 100644 --- a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c +++ b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -211,6 +212,16 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], + + // Check if `system_linker_exec` is required. + int shouldEnableSystemLinkerExecResult = shouldEnableSystemLinkerExecForFile(executablePath); + if (shouldEnableSystemLinkerExecResult < 0) { + return -1; + } + bool shouldEnableSystemLinkerExec = shouldEnableSystemLinkerExecResult == 0 ? true : false; + + + bool modifyEnv = false; bool unsetLdVarsFromEnv = shouldUnsetLDVarsFromEnv(info.isNonNativeElf, executablePath); logErrorVVerbose(LOG_TAG, "unset_ld_vars_from_env: '%d'", unsetLdVarsFromEnv); @@ -219,6 +230,29 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], modifyEnv = true; } + + + // If `system_linker_exec` is going to be used, then set `TERMUX_EXEC__PROC_SELF_EXE` + // environment variable to `processedExecutablePath`, otherwise + // unset it if it is already set. + char *envTermuxProcSelfExe = NULL; + if (shouldEnableSystemLinkerExec) { + modifyEnv = true; + logErrorVVerbose(LOG_TAG, "set_proc_self_exe_var_in_env: '%d'", true); + + if (asprintf(&envTermuxProcSelfExe, "%s%s", ENV_PREFIX__TERMUX_EXEC__PROC_SELF_EXE, processedExecutablePath) == -1) { + errno = ENOMEM; + logStrerrorDebug(LOG_TAG, "asprintf failed for '%s%s'", ENV_PREFIX__TERMUX_EXEC__PROC_SELF_EXE, processedExecutablePath); + return -1; + } + } else { + const char *proc_self_exe_var[] = { ENV_PREFIX__TERMUX_EXEC__PROC_SELF_EXE }; + if (areVarsInEnv(envp, proc_self_exe_var, 1)) { + logErrorVVerbose(LOG_TAG, "unset_proc_self_exe_var_from_env: '%d'", true); + modifyEnv = true; + } + } + logErrorVVerbose(LOG_TAG, "modify_env: '%d'", modifyEnv); @@ -237,13 +271,13 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], - const bool modifyArgs = interpreterSet; + const bool modifyArgs = shouldEnableSystemLinkerExec || interpreterSet; logErrorVVerbose(LOG_TAG, "modify_args: '%d'", modifyArgs); const char **newArgv = NULL; if (modifyArgs) { if (modifyExecArgs(argv, &newArgv, origExecutablePath, executablePath, - interpreterSet, &info) != 0 || + interpreterSet, shouldEnableSystemLinkerExec, &info) != 0 || newArgv == NULL) { logErrorDebug(LOG_TAG, "Failed to create modified exec args"); free(envTermuxProcSelfExe); @@ -251,6 +285,11 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], return -1; } + // Replace executable path if wrapping with linker. + if (shouldEnableSystemLinkerExec) { + executablePath = SYSTEM_LINKER_PATH; + } + argv = (char **) newArgv; } @@ -383,7 +422,6 @@ int inspectFileHeader(const char *termuxPrefixDir, char *header, size_t headerLe bool verboseLoggingEnabled = getCurrentLogLevel() >= LOG_LEVEL__VERBOSE; - (void)verboseLoggingEnabled; size_t interpreterPathBufferSize = sizeof(info->interpreterPathBuffer); char interpreterPathBuffer[strlen(interpreter) + 1]; @@ -568,7 +606,7 @@ int modifyExecEnv(char *const *envp, char ***newEnvpPointer, int modifyExecArgs(char *const *argv, const char ***newArgvPointer, const char *origExecutablePath, const char *executablePath, - bool interpreterSet, struct TermuxFileHeaderInfo *info) { + bool interpreterSet, bool shouldEnableSystemLinkerExec, struct TermuxFileHeaderInfo *info) { int argsCount = 0; while (argv[argsCount] != NULL) { argsCount++; @@ -594,6 +632,11 @@ int modifyExecArgs(char *const *argv, const char ***newArgvPointer, newArgv[index++] = argv[0]; } + // Add executable path if wrapping with linker. + if (shouldEnableSystemLinkerExec) { + newArgv[index++] = executablePath; + } + // Add interpreter argument and script path if executing a script with shebang. if (interpreterSet) { if (info->interpreterArg != NULL) { diff --git a/lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c b/lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c index a4ac2dd..bc3e85a 100644 --- a/lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c +++ b/lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c @@ -30,3 +30,18 @@ int termuxExec_execveCall_intercept_get() { } return def; } + +int termuxExec_systemLinkerExec_mode_get() { + int def = ENV_DEF_VAL__TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE; + const char* value = getenv(ENV__TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE); + if (value == NULL || strlen(value) < 1) { + return def; + } else if (strcmp(value, "disable") == 0) { + return 0; + } else if (strcmp(value, "enable") == 0) { + return 1; + } else if (strcmp(value, "force") == 0) { + return 2; + } + return def; +} diff --git a/packaging/debian/postinst.in b/packaging/debian/postinst.in new file mode 100644 index 0000000..049fcea --- /dev/null +++ b/packaging/debian/postinst.in @@ -0,0 +1,21 @@ +#!@TERMUX__PREFIX@/bin/sh +# shellcheck shell=sh +# shellcheck disable=SC2039,SC2059,SC3043 + +log() { echo "termux-exec.postinst:" "$@"; } +log_error() { echo "termux-exec.postinst:" "$@" 1>&2; } + +## +# `main` [``] +## +main() { + + log "Start" + + termux-exec-ld-preload-lib setup -v || return $? + + log "End" + +} + +main "$@" From ad67e020fda02b75001eb8ed97f5ae932c1c0fc1 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 22 Mar 2025 02:22:41 +0500 Subject: [PATCH 18/47] Added: Add testing framework via `app/main/tests/termux-exec-tests.in` and re-add some tests initially added in 2fe47750 and removed in 1fac1073 - Move unit tests from `src/termux-exec.c` to `app/main/tests/TermuxExecUnitTests.c` since tests shouldn't be in source files. - Added testing framework via `app/main/tests/termux-exec-tests.in` and `lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in` that calls `termux-exec/lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c` to run unit tests, and `lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c` and `lib/termux-exec_nos_c_tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in` for runtime tests. Old tests files in random places have been removed. The entire `exec()` family of functions is also tested by `lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c`. Docs will be added in a later commit.Tests can be run with `"${TERMUX__PREFIX:-$PREFIX}/libexec/installed-tests/termux-exec/app/main/termux-exec-tests -vv all"`. --- Makefile | 111 ++- app/main/tests/termux-exec-tests.in | 739 ++++++++++++++++++ .../termux_exec/TermuxExecShellEnvironment.h | 49 ++ .../termux_exec/TermuxExecShellEnvironment.c | 6 + .../tests/libtermux-exec_nos_c_tre_tests.in | 192 +++++ ...mux-exec_nos_c_tre_runtime-script-tests.in | 43 + .../exec-intercept_runtime-script-tests.in | 420 ++++++++++ .../direct/exec/files/print-args-binary.c | 11 + .../direct/exec/files/print-args-binary.sym | 1 + .../exec/files/print-args-linux-script.sh | 3 + .../exec/files/print-args-linux-script.sh.sym | 1 + .../exec/files/print-args-termux-script.sh.in | 3 + .../files/print-args-termux-script.sh.sym | 1 + ...rmux-exec_nos_c_tre_runtime-binary-tests.c | 119 +++ ...btermux-exec_nos_c_tre_unit-binary-tests.c | 80 ++ .../exec/ExecIntercept_RuntimeBinaryTests.c | 470 +++++++++++ .../exec/ExecIntercept_UnitBinaryTests.c | 207 +++++ run-tests.sh | 20 - test-program.c | 75 -- tests/args-with-spaces.sh | 1 - tests/args-with-spaces.sh-expected | 1 - tests/initial-whitespace.sh | 3 - tests/initial-whitespace.sh-expected | 1 - tests/not-executable.sh | 3 - tests/not-executable.sh-expected | 1 - tests/simple.sh | 3 - tests/simple.sh-expected | 1 - tests/usr-bin-env.sh | 3 - tests/usr-bin-env.sh-expected | 1 - 29 files changed, 2455 insertions(+), 114 deletions(-) create mode 100644 app/main/tests/termux-exec-tests.in create mode 100644 lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in create mode 100644 lib/termux-exec_nos_c_tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in create mode 100644 lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/exec-intercept_runtime-script-tests.in create mode 100644 lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary.c create mode 120000 lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary.sym create mode 100644 lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh create mode 120000 lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh.sym create mode 100644 lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-termux-script.sh.in create mode 120000 lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-termux-script.sh.sym create mode 100644 lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c create mode 100644 lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c create mode 100644 lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c create mode 100644 lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c delete mode 100755 run-tests.sh delete mode 100644 test-program.c delete mode 100755 tests/args-with-spaces.sh delete mode 100644 tests/args-with-spaces.sh-expected delete mode 100755 tests/initial-whitespace.sh delete mode 100644 tests/initial-whitespace.sh-expected delete mode 100644 tests/not-executable.sh delete mode 100644 tests/not-executable.sh-expected delete mode 100755 tests/simple.sh delete mode 100644 tests/simple.sh-expected delete mode 100755 tests/usr-bin-env.sh delete mode 100644 tests/usr-bin-env.sh-expected diff --git a/Makefile b/Makefile index 4489d18..680fda0 100644 --- a/Makefile +++ b/Makefile @@ -269,6 +269,12 @@ build-termux-exec-main-app: find app/main/scripts -type l -exec cp -a "{}" $(BIN_BUILD_OUTPUT_DIR)/ \; + @printf "\ntermux-exec-package: %s\n" "Building app/main/tests/*" + @mkdir -p $(TERMUX_EXEC__MAIN_APP__TESTS_BUILD_OUTPUT_DIR) + find app/main/tests -maxdepth 1 -type f -name "*.in" -print0 | xargs -0 -n1 sh -c \ + 'output_file="$(TERMUX_EXEC__MAIN_APP__TESTS_BUILD_OUTPUT_DIR)/$$(printf "%s" "$$0" | sed -e "s|^app/main/tests/||" -e "s/\.in$$//")" && mkdir -p "$$(dirname "$$output_file")" && sed $(TERMUX__CONSTANTS__SED_ARGS) "$$0" > "$$output_file"' + find $(TERMUX_EXEC__MAIN_APP__TESTS_BUILD_OUTPUT_DIR) -maxdepth 1 -type f -exec chmod 700 "{}" \; + build-libtermux-exec_nos_c_tre: @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre" @mkdir -p $(LIB_BUILD_OUTPUT_DIR) @@ -295,6 +301,76 @@ build-libtermux-exec_nos_c_tre: @printf "\ntermux-exec-package: %s\n" "Building lib/libtermux-exec_nos_c_tre.a" $(AR) rcs $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec_nos_c_tre.a $(LIBTERMUX_EXEC__NOS__C__OBJECT_FILES) + + + + @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/tests/*" + @mkdir -p $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR) + + + @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests" + $(call replace-termux-constants,lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests,$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)) + chmod 700 $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/libtermux-exec_nos_c_tre_tests + + + @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/tests/bin/libtermux-exec_nos_c_tre_unit-binary-tests" + @mkdir -p $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin + + @# `nm --demangle --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/bin/libtermux-exec_nos_c_tre_unit-binary-tests-fsanitize` + $(TERMUX_EXEC_EXECUTABLE__C__BUILD_COMMAND) -O0 -g \ + $(FSANTIZE_FLAGS) \ + -o $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin/libtermux-exec_nos_c_tre_unit-binary-tests-fsanitize \ + lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c \ + $(TERMUX_EXEC_EXECUTABLE__C__POST_LDFLAGS) + + @# `nm --demangle --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/bin/libtermux-exec_nos_c_tre_unit-binary-tests-nofsanitize` + $(TERMUX_EXEC_EXECUTABLE__C__BUILD_COMMAND) -O0 -g \ + -o $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin/libtermux-exec_nos_c_tre_unit-binary-tests-nofsanitize \ + lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c \ + $(TERMUX_EXEC_EXECUTABLE__C__POST_LDFLAGS) + + + @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/tests/bin/libtermux-exec_nos_c_tre_runtime-binary-tests$(TERMUX_EXEC_PKG__TESTS__API_LEVEL)" + @mkdir -p $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin + + $(TERMUX_EXEC_EXECUTABLE__C__BUILD_COMMAND) -O0 -g \ + $(FSANTIZE_FLAGS) \ + -o $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin/libtermux-exec_nos_c_tre_runtime-binary-tests-fsanitize$(TERMUX_EXEC_PKG__TESTS__API_LEVEL) \ + lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c \ + $(TERMUX_EXEC_EXECUTABLE__C__POST_LDFLAGS) + $(TERMUX_EXEC_EXECUTABLE__C__BUILD_COMMAND) -O0 -g \ + -o $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin/libtermux-exec_nos_c_tre_runtime-binary-tests-nofsanitize$(TERMUX_EXEC_PKG__TESTS__API_LEVEL) \ + lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c \ + $(TERMUX_EXEC_EXECUTABLE__C__POST_LDFLAGS) + + + @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/tests/scripts/*" + @mkdir -p $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts + find lib/termux-exec_nos_c_tre/tests/scripts -type f -name '*.c' -print0 | xargs -0 -n1 sh -c \ + 'output_file="$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts/$$(printf "%s" "$$0" | sed -e "s|^lib/termux-exec_nos_c_tre/tests/scripts/||" -e "s/\.c$$//")" && mkdir -p "$$(dirname "$$output_file")" && $(CC) $(CFLAGS) -O0 -fPIE -pie $(LDFLAGS) -g "$$0" -o "$$output_file"' + find lib/termux-exec_nos_c_tre/tests/scripts -type f -name '*.sh' -print0 | xargs -0 -n1 sh -c \ + 'output_file="$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts/$$(printf "%s" "$$0" | sed -e "s|^lib/termux-exec_nos_c_tre/tests/scripts/||")" && mkdir -p "$$(dirname "$$output_file")" && cp -a "$$0" "$$output_file"' + find lib/termux-exec_nos_c_tre/tests/scripts -type f -name "*.in" -print0 | xargs -0 -n1 sh -c \ + 'output_file="$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts/$$(printf "%s" "$$0" | sed -e "s|^lib/termux-exec_nos_c_tre/tests/scripts/||" -e "s/\.in$$//")" && mkdir -p "$$(dirname "$$output_file")" && sed $(TERMUX__CONSTANTS__SED_ARGS) "$$0" > "$$output_file"' + find lib/termux-exec_nos_c_tre/tests/scripts -type l -print0 | xargs -0 -n1 sh -c \ + 'output_file="$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts/$$(printf "%s" "$$0" | sed -e "s|^lib/termux-exec_nos_c_tre/tests/scripts/||")" && mkdir -p "$$(dirname "$$output_file")" && cp -a "$$0" "$$output_file"' + find $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts -type f -exec chmod 700 "{}" \; + + +build-libtermux-exec_nos_c_tre_runtime-binary-tests: + @printf "termux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/tests/bin/libtermux-exec_nos_c_tre_runtime-binary-tests$(TERMUX_EXEC_PKG__TESTS__API_LEVEL)" + @mkdir -p $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin + + $(TERMUX_EXEC_EXECUTABLE__C__BUILD_COMMAND) -O0 -g \ + $(FSANTIZE_FLAGS) \ + -o $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin/libtermux-exec_nos_c_tre_runtime-binary-tests-fsanitize$(TERMUX_EXEC_PKG__TESTS__API_LEVEL) \ + lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c \ + $(TERMUX_EXEC_EXECUTABLE__C__POST_LDFLAGS) + $(TERMUX_EXEC_EXECUTABLE__C__BUILD_COMMAND) -O0 -g \ + -o $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin/libtermux-exec_nos_c_tre_runtime-binary-tests-nofsanitize$(TERMUX_EXEC_PKG__TESTS__API_LEVEL) \ + lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c \ + $(TERMUX_EXEC_EXECUTABLE__C__POST_LDFLAGS) + build-libtermux-exec-direct-ld-preload: @mkdir -p $(LIB_BUILD_OUTPUT_DIR) @@ -381,6 +457,10 @@ install: find $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec -type f -exec chmod 600 {} \; + rm -rf $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/libexec/installed-tests/termux-exec + install -d $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/libexec/installed-tests + cp -a $(TESTS_BUILD_OUTPUT_DIR) $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/libexec/installed-tests/termux-exec + @printf "\ntermux-exec-package: %s\n\n" "Install termux-exec-package successful" uninstall: @@ -400,6 +480,9 @@ uninstall: rm -f $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec-linker-ld-preload.so rm -f $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec.so + + rm -rf $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/libexec/installed-tests/termux-exec + @printf "\ntermux-exec-package: %s\n\n" "Uninstall termux-exec-package successful" @@ -409,6 +492,32 @@ packaging-debian-build: all +test: all + $(MAKE) TERMUX_EXEC_PKG__INSTALL_PREFIX=$(PREFIX_BUILD_INSTALL_DIR) install + + @printf "\ntermux-exec-package: %s\n" "Executing termux-exec-package tests" + bash $(PREFIX_BUILD_INSTALL_DIR)/libexec/installed-tests/termux-exec/app/main/termux-exec-tests \ + --tests-path="$(PREFIX_BUILD_INSTALL_DIR)/libexec/installed-tests/termux-exec" \ + --ld-preload-dir="$(PREFIX_BUILD_INSTALL_DIR)/lib" \ + -vvv all + +test-unit: all + $(MAKE) TERMUX_EXEC_PKG__INSTALL_PREFIX=$(PREFIX_BUILD_INSTALL_DIR) install + + @printf "\ntermux-exec-package: %s\n" "Executing termux-exec-package unit tests" + bash $(PREFIX_BUILD_INSTALL_DIR)/libexec/installed-tests/termux-exec/app/main/termux-exec-tests \ + --tests-path="$(PREFIX_BUILD_INSTALL_DIR)/libexec/installed-tests/termux-exec" \ + -vvv unit + +test-runtime: all + $(MAKE) TERMUX_EXEC_PKG__INSTALL_PREFIX=$(PREFIX_BUILD_INSTALL_DIR) install + + @printf "\ntermux-exec-package: %s\n" "Executing termux-exec-package runtime tests" + bash $(PREFIX_BUILD_INSTALL_DIR)/libexec/installed-tests/termux-exec/app/main/termux-exec-tests \ + --tests-path="$(PREFIX_BUILD_INSTALL_DIR)/libexec/installed-tests/termux-exec" \ + --ld-preload-dir="$(PREFIX_BUILD_INSTALL_DIR)/lib" \ + -vvv runtime + format: @@ -422,4 +531,4 @@ check: -.PHONY: all pre-build build-termux-exec-main-app build-libtermux-exec_nos_c_tre build-libtermux-exec-direct-ld-preload build-libtermux-exec-linker-ld-preload clean install uninstall packaging-debian-build format check +.PHONY: all pre-build build-termux-exec-main-app build-libtermux-exec_nos_c_tre build-libtermux-exec_nos_c_tre_runtime-binary-tests build-libtermux-exec-linker-ld-preload build-libtermux-exec-direct-ld-preload clean install uninstall packaging-debian-build test test-unit test-runtime format check diff --git a/app/main/tests/termux-exec-tests.in b/app/main/tests/termux-exec-tests.in new file mode 100644 index 0000000..fe06a58 --- /dev/null +++ b/app/main/tests/termux-exec-tests.in @@ -0,0 +1,739 @@ +#!@TERMUX__PREFIX@/bin/bash +# shellcheck shell=bash + +if [ -z "${BASH_VERSION:-}" ]; then + echo "The 'termux-exec-tests' script must be run from a 'bash' shell."; return 64 2>/dev/null|| exit 64 # EX__USAGE +fi + + + +termux_exec__tests__init() { + +TERMUX_EXEC__TESTS__LOG_LEVEL___N="@TERMUX_ENV__S_TERMUX_EXEC__TESTS@LOG_LEVEL" +termux_exec__tests__copy_variable TERMUX_EXEC__TESTS__LOG_LEVEL "$TERMUX_EXEC__TESTS__LOG_LEVEL___N" || return $? + +TERMUX_EXEC__TESTS__MAX_LOG_LEVEL=5 # Default: `5` (VVVERBOSE=5) +{ [[ ! "${TERMUX_EXEC__TESTS__LOG_LEVEL:-}" =~ ^[0-9]+$ ]] || [[ "$TERMUX_EXEC__TESTS__LOG_LEVEL" -gt "$TERMUX_EXEC__TESTS__MAX_LOG_LEVEL" ]]; } && \ +TERMUX_EXEC__TESTS__LOG_LEVEL=1 # Default: `1` (OFF=0, NORMAL=1, DEBUG=2, VERBOSE=3, VVERBOSE=4 and VVVERBOSE=5) +TERMUX_EXEC__TESTS__LOG_TAG="" # Default: `` + +TERMUX_EXEC__TESTS__COMMAND_TYPE_ID="" # Default: `` +TERMUX_EXEC__TESTS__COMMAND_TYPE_NOOP="false" # Default: `false` + +NL=$'\n' + +TERMUX_EXEC__TESTS__DETECT_LEAKS=0 # Default: `0` +TERMUX_EXEC__TESTS__USE_FSANITIZE_BUILDS="false" # Default: `false` +TERMUX_EXEC__TESTS__NO_CLEAN="false" # Default: `false` +TERMUX_EXEC__TESTS__TEST_NAMES_FILTER="" + +TERMUX_EXEC__TESTS__TESTS_COUNT="" +TERMUX_EXEC__TESTS__LOG_EMPTY_LINE_AFTER_SCRIPT_TEST="false" + +TERMUX_EXEC__TESTS__REGEX__ABSOLUTE_PATH='^(/[^/]+)+$' +TERMUX_EXEC__TESTS__REGEX__ROOTFS_OR_ABSOLUTE_PATH='^((/)|((/[^/]+)+))$' +TERMUX_EXEC__TESTS__REGEX__UNSIGNED_INT='^[0-9]+$' + + + +# Set `TERMUX_*` variables to environment variables exported by +# Termux app, otherwise default to build time placeholders. +# This is done to support scoped and dynamic variables design. +# The `TERMUX_ENV__*` variables still use build time placeholders. + +TERMUX_APP__NAME___N="@TERMUX_ENV__S_TERMUX_APP@NAME" +termux_exec__tests__copy_variable TERMUX_APP__NAME "$TERMUX_APP__NAME___N" || return $? +[[ -z "$TERMUX_APP__NAME" ]] && \ +TERMUX_APP__NAME="@TERMUX_APP__NAME@" + + +TERMUX__ROOTFS___N="@TERMUX_ENV__S_TERMUX@ROOTFS" +termux_exec__tests__copy_variable TERMUX__ROOTFS "$TERMUX__ROOTFS___N" || return $? +[[ ! "$TERMUX__ROOTFS" =~ $TERMUX_EXEC__TESTS__REGEX__ROOTFS_OR_ABSOLUTE_PATH ]] && \ +TERMUX__ROOTFS="@TERMUX__ROOTFS@" + +TERMUX__PREFIX___N="@TERMUX_ENV__S_TERMUX@PREFIX" +termux_exec__tests__copy_variable TERMUX__PREFIX "$TERMUX__PREFIX___N" || return $? +[[ ! "$TERMUX__PREFIX" =~ $TERMUX_EXEC__TESTS__REGEX__ABSOLUTE_PATH ]] && \ +TERMUX__PREFIX="@TERMUX__PREFIX@" + + +TERMUX_EXEC__TESTS__TESTS_PATH___N="@TERMUX_ENV__S_TERMUX_EXEC__TESTS@TESTS_PATH" +TERMUX_EXEC__TESTS__TESTS_PATH="$TERMUX__PREFIX/libexec/installed-tests/termux-exec" +printf -v "$TERMUX_EXEC__TESTS__TESTS_PATH___N" "%s" "$TERMUX_EXEC__TESTS__TESTS_PATH" || return $? +export "${TERMUX_EXEC__TESTS__TESTS_PATH___N?}" || return $? + + +TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH___N="@TERMUX_ENV__S_TERMUX_EXEC__TESTS@PRIMARY_LD_PRELOAD_FILE_PATH" + +TERMUX_EXEC__TESTS__LD_PRELOAD_DIR="$TERMUX__PREFIX/lib" +TERMUX_EXEC__PRIMARY_LD_PRELOAD_FILE_PATH="$TERMUX__PREFIX/lib/libtermux-exec-ld-preload.so" +TERMUX_EXEC__DIRECT_LD_PRELOAD_FILE_PATH="$TERMUX__PREFIX/lib/libtermux-exec-direct-ld-preload.so" +TERMUX_EXEC__LINKER_LD_PRELOAD_FILE_PATH="$TERMUX__PREFIX/lib/libtermux-exec-linker-ld-preload.so" + + +# Set exit traps. +termux_exec__tests__set_traps || return $? + +} + + + +function termux_exec__tests__log() { local log_level="${1}"; shift; if [[ $TERMUX_EXEC__TESTS__LOG_LEVEL -ge $log_level ]]; then echo "${TERMUX_EXEC__TESTS__LOG_TAG:-"@TERMUX__LNAME@-exec.tests"}:" "$@"; fi } +function termux_exec__tests__log_literal() { local log_level="${1}"; shift; if [[ $TERMUX_EXEC__TESTS__LOG_LEVEL -ge $log_level ]]; then echo -e "${TERMUX_EXEC__TESTS__LOG_TAG:-"@TERMUX__LNAME@-exec.tests"}:" "$@"; fi } +function termux_exec__tests__log_error() { echo "${TERMUX_EXEC__TESTS__LOG_TAG:-"@TERMUX__LNAME@-exec.tests"}:" "$@" 1>&2; } + + + +## +# `termux_exec__tests__main` [``] +## +termux_exec__tests__main() { + + local return_value + + termux_exec__tests__init || return $? + + TERMUX_EXEC__TESTS__RUN_UNIT_TESTS="false" + TERMUX_EXEC__TESTS__RUN_RUNTIME_TESTS="false" + + # Process the command arguments passed to the script. + termux_exec__tests__process_script_arguments "$@" || return $? + if [ "$TERMUX_EXEC__TESTS__COMMAND_TYPE_NOOP" = "true" ]; then return 0; fi + + + termux_exec__tests__log 4 "Running 'termux_exec__tests__main'" + + if [[ "$TERMUX_EXEC__TESTS__COMMAND_TYPE_ID" == *,* ]] || \ + [[ ",unit,runtime,all," != *",$TERMUX_EXEC__TESTS__COMMAND_TYPE_ID,"* ]]; then + termux_exec__tests__log_error "Invalid command type id '$TERMUX_EXEC__TESTS__COMMAND_TYPE_ID' passed. Must equal 'unit', 'runtime' or 'all'." + return 1 + fi + + + termux_exec__tests__log 1 "Running 'termux-exec' tests" + + local tests_start_time; tests_start_time="$(date "+%s")" || return $? + + + [[ ",unit,all," == *",$TERMUX_EXEC__TESTS__COMMAND_TYPE_ID,"* ]] && TERMUX_EXEC__TESTS__RUN_UNIT_TESTS="true" + [[ ",runtime,all," == *",$TERMUX_EXEC__TESTS__COMMAND_TYPE_ID,"* ]] && TERMUX_EXEC__TESTS__RUN_RUNTIME_TESTS="true" + + + termux_exec__tests__log 5 "$TERMUX_EXEC__TESTS__LOG_LEVEL___N='$TERMUX_EXEC__TESTS__LOG_LEVEL'" + termux_exec__tests__log 5 "$TERMUX_EXEC__TESTS__TESTS_PATH___N='$TERMUX_EXEC__TESTS__TESTS_PATH'" + [[ -n "$TERMUX_EXEC__TESTS__TEST_NAMES_FILTER" ]] && termux_exec__tests__log 5 "$TERMUX_EXEC__TESTS__TEST_NAMES_FILTER='$TERMUX_EXEC__TESTS__TEST_NAMES_FILTER'" + + + # Set `TERMUX_EXEC__TESTS__TESTS_PATH` used by compiled c tests. + if [[ ! "$TERMUX_EXEC__TESTS__TESTS_PATH" =~ $TERMUX_EXEC__TESTS__REGEX__ROOTFS_OR_ABSOLUTE_PATH ]]; then + termux_exec__tests__log_error "The TERMUX_EXEC__TESTS__TESTS_PATH '$TERMUX_EXEC__TESTS__TESTS_PATH' is either not set or is not an absolute path" + return 1 + fi + + + TERMUX_EXEC__TESTS__IS_RUNNING_ON_ANDROID="false" + TERMUX_EXEC__TESTS__IS_RUNNING_IN_TERMUX="false" + if [ -f "/system/bin/app_process" ]; then + TERMUX_EXEC__TESTS__IS_RUNNING_ON_ANDROID="true" + [ -x "$TERMUX__ROOTFS" ] && TERMUX_EXEC__TESTS__IS_RUNNING_IN_TERMUX="true" + fi + + + # Setup variables for runtime tests. + if [[ "$TERMUX_EXEC__TESTS__RUN_RUNTIME_TESTS" == "true" ]]; then + if [[ "$TERMUX_EXEC__TESTS__IS_RUNNING_IN_TERMUX" != "true" ]]; then + termux_exec__tests__log_error "The TERMUX__ROOTFS '$TERMUX__ROOTFS' path not found or is not executable. " + termux_exec__tests__log_error "Runtime tests must be run from $TERMUX_APP__NAME app in Android." + return 1 + fi + + + ANDROID__BUILD_VERSION_SDK="$(getprop "ro.build.version.sdk")" + if [[ ! "$ANDROID__BUILD_VERSION_SDK" =~ $TERMUX_EXEC__TESTS__REGEX__UNSIGNED_INT ]]; then + termux_exec__tests__log_error "Failed to get android build version sdk with getprop" + return 1 + fi + + + # Find the `libtermux-exec-*-ld-preload.so` variant currently + # installed at `$TERMUX__PREFIX/lib/libtermux-exec-ld-preload.so` and use + # the same variant under `$TERMUX_EXEC__TESTS__LD_PRELOAD_DIR`. + local primary_ld_preload_file_checksum direct_ld_preload_file_checksum linker_ld_preload_file_checksum + + primary_ld_preload_file_checksum="$(sha256sum "$TERMUX_EXEC__PRIMARY_LD_PRELOAD_FILE_PATH")" || return $? + primary_ld_preload_file_checksum="${primary_ld_preload_file_checksum%% *}" + direct_ld_preload_file_checksum="$(sha256sum "$TERMUX_EXEC__DIRECT_LD_PRELOAD_FILE_PATH")" || return $? + direct_ld_preload_file_checksum="${direct_ld_preload_file_checksum%% *}" + linker_ld_preload_file_checksum="$(sha256sum "$TERMUX_EXEC__LINKER_LD_PRELOAD_FILE_PATH")" || return $? + linker_ld_preload_file_checksum="${linker_ld_preload_file_checksum%% *}" + + if [[ "$primary_ld_preload_file_checksum" == "$direct_ld_preload_file_checksum" ]]; then + TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH="$TERMUX_EXEC__TESTS__LD_PRELOAD_DIR/libtermux-exec-direct-ld-preload.so" + elif [[ "$primary_ld_preload_file_checksum" == "$linker_ld_preload_file_checksum" ]]; then + TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH="$TERMUX_EXEC__TESTS__LD_PRELOAD_DIR/libtermux-exec-linker-ld-preload.so" + else + termux_exec__tests__log_error "Failed to find $TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH___N \ +to use for tests as checksum of primary ld preload file did not match a variant." + termux_exec__tests__log_error "TERMUX_EXEC__PRIMARY_LD_PRELOAD_FILE_PATH='$TERMUX_EXEC__PRIMARY_LD_PRELOAD_FILE_PATH' ($primary_ld_preload_file_checksum)" + termux_exec__tests__log_error "TERMUX_EXEC__DIRECT_LD_PRELOAD_FILE_PATH='$TERMUX_EXEC__DIRECT_LD_PRELOAD_FILE_PATH' ($direct_ld_preload_file_checksum)" + termux_exec__tests__log_error "TERMUX_EXEC__LINKER_LD_PRELOAD_FILE_PATH='$TERMUX_EXEC__LINKER_LD_PRELOAD_FILE_PATH' ($linker_ld_preload_file_checksum)" + return 1 + fi + + termux_exec__tests__log 5 "$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH___N='$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH'" + + if [[ ! -f "$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH" ]]; then + termux_exec__tests__log_error "The $TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH___N '$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH' not found." + return 1 + fi + + printf -v "$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH___N" "%s" "$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH" || return $? + export "${TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH___N?}" || return $? + + + if [[ ! -d "$TMPDIR" ]]; then + termux_exec__tests__log_error "The TMPDIR '$TMPDIR' is either not set or not a directory" + return 1 + fi + + + TERMUX_EXEC__TESTS__TMPDIR_PATH="$TMPDIR/termux-exec-tests" + + # Ensure test directory is clean and does not contain files from previous run. + rm -rf "$TERMUX_EXEC__TESTS__TMPDIR_PATH" || return $? + mkdir -p "$TERMUX_EXEC__TESTS__TMPDIR_PATH" || return $? + + + # Setup temp directory for exec tests. + # DO NOT modify as its used by `testExecIntercept__SingleAndDoubleDotExecutablePaths()`. + TERMUX_EXEC__TESTS__EXEC_TMPDIR_PATH="$TERMUX_EXEC__TESTS__TMPDIR_PATH/exec" + mkdir -p "$TERMUX_EXEC__TESTS__EXEC_TMPDIR_PATH" || return $? + + TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME="test-script" + TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH="$TERMUX_EXEC__TESTS__EXEC_TMPDIR_PATH/$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" + fi + + + + # Run tests. + termux_exec__libtermux_exec__nos__c__tests__run_command || return $? + + + + termux_exec__tests__log 1 "All 'termux-exec' tests successful in \ +$(termux_exec__tests__print_elapsed_time "$tests_start_time")" + + return 0 + +} + + +## +# `termux_exec__libtermux_exec__nos__c__tests__run_command` +## +termux_exec__libtermux_exec__nos__c__tests__run_command() { + + local return_value + + local tests_start_time; tests_start_time="$(date "+%s")" || return $? + + termux_exec__tests__log 1 "Running 'libtermux-exec_nos_c_tre_tests'" + + ( + # shellcheck source=lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in + termux_exec__tests__source_file_from_path \ + "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/libtermux-exec_nos_c_tre_tests" || exit $? + + libtermux_exec__nos__c__tests__main + ) + return_value=$? + if [ $return_value -ne 0 ]; then + termux_exec__tests__log_error "'libtermux-exec_nos_c_tre_tests' failed" + return $return_value + fi + + termux_exec__tests__log 2 "'libtermux-exec_nos_c_tre_tests' completed in \ +$(termux_exec__tests__print_elapsed_time "$tests_start_time")" + + return 0 + +} + + + + + +## +# `termux_exec__tests__run_script_test` `` [``] \ +# `` +# `` `` \ +# [``] +## +termux_exec__tests__run_script_test() { + + local return_value + + local opt; local opt_arg; local OPTARG; local OPTIND; + + local test_file_set_executable="true" + local working_directory="." + local execution_path="" + + if [[ $# -lt 1 ]]; then + termux_exec__tests__log_error "Invalid argument count $#. The 'termux_exec__tests__run_script_test' command expects at least 1 argument: \ + test_name" + return 1 + fi + + local test_name="$1" + shift 1 + + termux_exec__tests__log 5 "$test_name()" + + # Parse options to main command. + while getopts ":-:" opt; do + opt_arg="${OPTARG:-}" + case "${opt}" in + -) + case "${OPTARG}" in *?=*) opt_arg="${OPTARG#*=}";; *) opt_arg="";; esac + case "${OPTARG}" in + no-test-file-set-executable) + test_file_set_executable="false" + ;; + working-dir=?*) + working_directory="$opt_arg" + ;; + working-dir | working-dir=) + termux_exec__tests__log_error "No argument set for arg option '--${OPTARG%=*}'." + return 64 # EX__USAGE + ;; + executable-path=?*) + execution_path="$opt_arg" + ;; + executable-path | executable-path=) + termux_exec__tests__log_error "No argument set for arg option '--${OPTARG%=*}'." + return 64 # EX__USAGE + ;; + '') + # End of options `--`. + break + ;; + *) + termux_exec__tests__log_error "Unknown option: '--${OPTARG:-}'." + return 64 # EX__USAGE + ;; + esac + ;; + \?) + :;; + esac + done + shift $((OPTIND - 1)) # Remove already processed arguments from argument list + + + + if [[ $# -lt 3 ]]; then + termux_exec__tests__log_error "Invalid argument count $#. The 'termux_exec__tests__run_script_test' command expects at least 4 arguments: \ + test_name test_file_content expected_exit_code expected_output_regex [script_args]" + return 1 + fi + + local test_file_content="$1" + local expected_exit_code="$2" + local expected_output_regex="$3" + shift 3 # Remove args before `script_args` + + local output + local actual_output + local test_failed="false" + + if [[ -n "${TERMUX_EXEC__TESTS__TEST_NAMES_FILTER:-}" ]] && [[ ! "$test_name" =~ $TERMUX_EXEC__TESTS__TEST_NAMES_FILTER ]]; then + return 0 + fi + + if [[ "${TERMUX_EXEC__TESTS__TESTS_COUNT:-}" =~ $TERMUX_EXEC__TESTS__REGEX__UNSIGNED_INT ]]; then + TERMUX_EXEC__TESTS__TESTS_COUNT=$((TERMUX_EXEC__TESTS__TESTS_COUNT + 1)) + fi + + termux_exec__tests__log 5 "test_file_path='$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH'" + if [[ "$test_file_content" == *"${NL}"* ]]; then + termux_exec__tests__log 5 "test_file_content=${NL}"'```'"${NL}$test_file_content${NL}"'```' + else + termux_exec__tests__log 5 "test_file_content='$test_file_content'" + fi + + if [[ "$test_file_set_executable" != "true" ]]; then + termux_exec__tests__log 5 "test_file_set_executable='$test_file_set_executable'" + fi + if [[ "$working_directory" != "." ]]; then + termux_exec__tests__log 5 "working_directory='$working_directory'" + fi + if [[ -n "$execution_path" ]]; then + termux_exec__tests__log 5 "execution_path='$execution_path'" + fi + + termux_exec__tests__log 5 "expected_exit_code='$expected_exit_code'" + termux_exec__tests__log 5 "expected_output_regex='$expected_output_regex'" + + # If TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH is not a valid absolute path. + if [[ ! "$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH" =~ $TERMUX_EXEC__TESTS__REGEX__ABSOLUTE_PATH ]]; then + termux_exec__tests__log_error "The TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH '$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH' is not a valid absolute path" + return 1 + fi + + rm -f "$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH" || return $? + + output="$(printf "%s" "$test_file_content" > "$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH" 2>&1)" + return_value=$? + if [ $return_value -ne 0 ]; then + printf "%s\n" "$output" 1>&2 + termux_exec__tests__log_error "Failed to create the '$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH' file for the '$test_name' test" + return $return_value + fi + + if [[ "$test_file_set_executable" == "true" ]]; then + output="$(chmod +x "$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH" 2>&1)" + return_value=$? + if [ $return_value -ne 0 ]; then + printf "%s\n" "$output" 1>&2 + termux_exec__tests__log_error "Failed to set the executable bit for the '$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH' file for the '$test_name' test" + return $return_value + fi + fi + + actual_output="$(cd "$working_directory" && "${execution_path:-$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH}" "$@" 2>&1)" + actual_exit_code=$? + if [[ -n "$expected_output_regex" ]] && [[ ! "$actual_output" =~ $expected_output_regex ]]; then + termux_exec__tests__log_error "FAILED: '$test_name' test" + termux_exec__tests__log_error "Expected output_regex does not equal match actual output" + test_failed="true" + elif [ $actual_exit_code != "$expected_exit_code" ]; then + termux_exec__tests__log_error "$actual_output" + termux_exec__tests__log_error "FAILED: '$test_name' test" + termux_exec__tests__log_error "Expected result_code does not equal actual result_code" + test_failed="true" + fi + + if [[ "$test_failed" == "true" ]]; then + if [[ "$test_file_content" == *"${NL}"* ]]; then + termux_exec__tests__log_error "test_file_content=${NL}"'```'"${NL}$test_file_content${NL}"'```' + else + termux_exec__tests__log_error "test_file_content='$test_file_content'" + fi + termux_exec__tests__log_error "actual_exit_code: '$actual_exit_code'" + termux_exec__tests__log_error "expected_exit_code: '$expected_exit_code'" + termux_exec__tests__log_error "actual_output: '$actual_output'" + termux_exec__tests__log_error "expected_output_regex: '$expected_output_regex'" + return 100 + else + #termux_exec__tests__log 2 "PASSED" + + # Remove test file so that later tests do not accidentally use it. + rm -f "$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH" || return $? + + if [[ "$TERMUX_EXEC__TESTS__LOG_EMPTY_LINE_AFTER_SCRIPT_TEST" == "true" ]]; then + termux_exec__tests__log 5 "" + fi + + return 0 + fi + +} + + + + + +## +# Source a file under `$PATH`, like under `TERMUX__PREFIX/bin`. +# +# A separate function is used to source so that arguments passed to +# calling script/function are not passed to the sourced script. +# +# +# termux_exec__tests__source_file_from_path +## +termux_exec__tests__source_file_from_path() { + + local source_file="${1:-}"; [ $# -gt 0 ] && shift 1; + + local source_path + + if source_path="$(command -v "$source_file")" && [ -n "$source_path" ]; then + # shellcheck disable=SC1090 + source "$source_path" || return $? + else + echo "Failed to find the '$source_file' file to source." 1>&2 + return 1 + fi + +} + + + +## +# Copy the value of a variable to another variable. +# +# +# **Parameters:** +# `output_variable_name` - The name of the output variable to set. +# `input_variable_name` - The name of the input variable to read. +# +# **Returns:** +# Returns `0` if successful, otherwise returns with a non-zero exit code. +# +# +# `termux_exec__tests__copy_variable` `` `` +## +termux_exec__tests__copy_variable() { + + local output_variable_name="${1:-}" + local input_variable_name="${2:-}" + + if [[ ! "$output_variable_name" =~ ^[a-zA-Z][a-zA-Z0-9_]*$ ]]; then + echo "The output_variable_name '$output_variable_name' is not a valid shell variable name while running 'termux_exec__tests__copy_variable'." 1>&2 + return 1 + fi + + if [[ ! "$input_variable_name" =~ ^[a-zA-Z][a-zA-Z0-9_]*$ ]]; then + echo "The input_variable_name '$input_variable_name' is not a valid shell variable name while running 'termux_exec__tests__copy_variable'." 1>&2 + return 1 + fi + + eval "$output_variable_name"=\"\$\{"$input_variable_name":-\}\" + +} + + + +## +# Escape '\$[](){}|^.?+*' in a string with backslashes so that it can +# be used as a literal string in regex. +# +# +# `termux_exec__tests__escape_string_for_regex` `` +## +termux_exec__tests__escape_string_for_regex() { + + printf "%s" "$1" | sed -zE -e 's/[][\.|$(){}?+*^]/\\&/g' + +} + + + +## +# `termux_exec__tests__print_elapsed_time` `` +## +termux_exec__tests__print_elapsed_time() { + + local start_time="$1" + + local end_time + + end_time=$(($(date "+%s") - start_time)) || return $? + + printf "%s" "$((end_time / 3600 )) hours $(((end_time % 3600) / 60)) minutes $((end_time % 60)) seconds" + +} + + + + + +## +# Set exit traps to `termux_exec__tests__traps()`. +## +termux_exec__tests__set_traps() { + + # Set traps to `termux_exec__tests__traps`. + trap 'termux_exec__tests__traps' EXIT + trap 'termux_exec__tests__traps TERM' TERM + trap 'termux_exec__tests__traps INT' INT + trap 'termux_exec__tests__traps HUP' HUP + trap 'termux_exec__tests__traps QUIT' QUIT + + return 0 + +} + +termux_exec__tests__traps_killtree() { + + local signal="$1"; local pid="$2"; local cpid + for cpid in $(pgrep -P "$pid"); do termux_exec__tests__traps_killtree "$signal" "$cpid"; done + [[ "$pid" != "$$" ]] && signal="${signal:=15}"; kill "-$signal" "$pid" 2>/dev/null + +} + +termux_exec__tests__traps() { + + local exit_code=$? + trap - EXIT + + if [[ "${TERMUX_EXEC__TESTS__TMPDIR_PATH:-}" =~ ^(/[^/]+)+$ ]] && [[ "$TERMUX_EXEC__TESTS__NO_CLEAN" != "true" ]]; then + rm -rf "$TERMUX_EXEC__TESTS__TMPDIR_PATH" + fi + + [ -n "${1:-}" ] && trap - "$1"; + termux_exec__tests__traps_killtree "${1:-}" $$; + exit $exit_code + +} + + + + + +## +# `termux_exec__tests__process_script_arguments` [``] +## +termux_exec__tests__process_script_arguments() { + + local opt; local opt_arg; local OPTARG; local OPTIND; + + if [ $# -eq 0 ]; then + TERMUX_EXEC__TESTS__COMMAND_TYPE_NOOP="true" + show_help; return $? + fi + + # Parse options to main command. + while getopts ":hqvfl-:" opt; do + opt_arg="${OPTARG:-}" + case "${opt}" in + -) + case "${OPTARG}" in *?=*) opt_arg="${OPTARG#*=}";; *) opt_arg="";; esac + case "${OPTARG}" in + help) + TERMUX_EXEC__TESTS__COMMAND_TYPE_NOOP="true" + show_help; return $? + ;; + version) + TERMUX_EXEC__TESTS__COMMAND_TYPE_NOOP="true" + echo "@TERMUX_EXEC_PKG__VERSION@"; return $? + ;; + quiet) + TERMUX_EXEC__TESTS__LOG_LEVEL=0 + ;; + ld-preload-dir=?*) + TERMUX_EXEC__TESTS__LD_PRELOAD_DIR="$(readlink -f -- "$opt_arg")" || return $? + ;; + ld-preload-dir | ld-preload-dir=) + termux_exec__tests__log_error "No argument set for arg option '--${OPTARG%=*}'." + return 64 # EX__USAGE + ;; + no-clean) + TERMUX_EXEC__TESTS__NO_CLEAN="true" + ;; + test-names-filter=?*) + TERMUX_EXEC__TESTS__TEST_NAMES_FILTER="$opt_arg" || return $? + ;; + test-names-filter | test-names-filter=) + termux_exec__tests__log_error "No argument set for arg option '--${OPTARG%=*}'." + return 64 # EX__USAGE + ;; + tests-path=?*) + TERMUX_EXEC__TESTS__TESTS_PATH="$(readlink -f -- "$opt_arg")" || return $? + ;; + tests-path | tests-path=) + termux_exec__tests__log_error "No argument set for arg option '--${OPTARG%=*}'." + return 64 # EX__USAGE + ;; + '') + # End of options `--`. + break + ;; + *) + termux_exec__tests__log_error "Unknown option: '--${OPTARG:-}'." + return 64 # EX__USAGE + ;; + esac + ;; + h) + TERMUX_EXEC__TESTS__COMMAND_TYPE_NOOP="true" + show_help; return $? + ;; + q) + TERMUX_EXEC__TESTS__LOG_LEVEL=0 + ;; + v) + if [ "$TERMUX_EXEC__TESTS__LOG_LEVEL" -lt "$TERMUX_EXEC__TESTS__MAX_LOG_LEVEL" ]; then + TERMUX_EXEC__TESTS__LOG_LEVEL=$((TERMUX_EXEC__TESTS__LOG_LEVEL+1)); + else + termux_exec__tests__log_error "Invalid option, max log level is $TERMUX_EXEC__TESTS__MAX_LOG_LEVEL." + return 64 # EX__USAGE + fi + ;; + f) + TERMUX_EXEC__TESTS__USE_FSANITIZE_BUILDS="true" + ;; + l) + TERMUX_EXEC__TESTS__DETECT_LEAKS=1 + ;; + \?) + :;; + esac + done + shift $((OPTIND - 1)) # Remove already processed arguments from argument list + + if [ $# -eq 0 ]; then + termux_exec__tests__log_error "The command type not passed." + return 64 # EX__USAGE + elif [ $# -ne 1 ]; then + termux_exec__tests__log_error "Expected 1 argument for command type but passed: $*" + return 64 # EX__USAGE + fi + + TERMUX_EXEC__TESTS__COMMAND_TYPE_ID="$1" + + return 0; + +} + +## +# `show_help` +## +show_help() { + + cat <<'HELP_EOF' +termux-exec-tests can be used to run tests for 'termux-exec'. + + +Usage: + termux-exec-tests [command_options] + +Available commands: + unit Run unit tests. + runtime Run runtime on-device tests. + all Run all tests. + +Available command_options: + [ -h | --help ] Display this help screen. + [ --version ] Display version. + [ -q | --quiet ] Set log level to 'OFF'. + [ -v | -vv | -vvv | -vvvvv ] + Set log level to 'DEBUG', 'VERBOSE', + 'VVERBOSE' and 'VVVERBOSE'. + [ -f ] Use fsanitize binaries for AddressSanitizer. + [ -l ] Detect memory leaks with LeakSanitizer. + Requires '-f' to be passed. + [ --ld-preload-dir= ] + The directory containing `$LD_PRELOAD' + libraries: 'libtermux-exec*.so'. + [ --no-clean ] Do not clean test files on failure. + [ --test-names-filter= ] + Regex to filter which tests to run by + test name. + [ --tests-path= ] The path to installed-tests directory. +HELP_EOF + +} + +# If script is sourced, return with success, otherwise call main function. +# - https://stackoverflow.com/a/28776166/14686958 +# - https://stackoverflow.com/a/29835459/14686958 +if (return 0 2>/dev/null); then + return 0 # EX__SUCCESS +else + termux_exec__tests__main "$@" + exit $? +fi diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h index 478dd3c..8789f9d 100644 --- a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h @@ -101,6 +101,42 @@ static const int ENV_DEF_VAL__TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE = 1; +/* + * Environment for `termux-exec-tests`. + */ + +/** + * Environment variable for the log level for `termux-exec-tests`. + * + * Type: `int` + * Default key: `TERMUX_EXEC__TESTS__LOG_LEVEL` + * Default value: DEFAULT_LOG_LEVEL + * Values: + * - `0` (`OFF`) - Log nothing. + * - `1` (`NORMAL`) - Log error, warn and info messages and stacktraces. + * - `2` (`DEBUG`) - Log debug messages. + * - `3` (`VERBOSE`) - Log verbose messages. + * - `4` (`VVERBOSE`) - Log very verbose messages. + */ +#define ENV__TERMUX_EXEC__TESTS__LOG_LEVEL TERMUX_ENV__S_TERMUX_EXEC__TESTS "LOG_LEVEL" + + + +/** + * Environment variable for the path to the termux-exec tests. + */ +#define ENV__TERMUX_EXEC__TESTS__TESTS_PATH TERMUX_ENV__S_TERMUX_EXEC__TESTS "TESTS_PATH" + +/** + * Environment variable for the path to the primary Termux `$LD_PRELOAD` + * library used for tests. + */ +#define ENV__TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH TERMUX_ENV__S_TERMUX_EXEC__TESTS "PRIMARY_LD_PRELOAD_FILE_PATH" + + + + + /** * Returns the `termux-exec` config for `Logger` log level * based on the `ENV__TERMUX_EXEC__LOG_LEVEL` env variable. @@ -138,6 +174,19 @@ int termuxExec_systemLinkerExec_mode_get(); + + +/** + * Returns the `termux-exec-tests` config for `Logger` log level + * based on the `ENV__TERMUX_EXEC__TESTS__LOG_LEVEL` env variable. + * + * @return Return the value if `ENV__TERMUX_EXEC__TESTS__LOG_LEVEL` is + * set, otherwise defaults to `DEFAULT_LOG_LEVEL`. + */ +int termuxExec_tests_logLevel_get(); + + + #ifdef __cplusplus } #endif diff --git a/lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c b/lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c index bc3e85a..04a9543 100644 --- a/lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c +++ b/lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c @@ -45,3 +45,9 @@ int termuxExec_systemLinkerExec_mode_get() { } return def; } + + + +int termuxExec_tests_logLevel_get() { + return getLogLevelFromEnv(ENV__TERMUX_EXEC__TESTS__LOG_LEVEL); +} diff --git a/lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in b/lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in new file mode 100644 index 0000000..7dfcfa8 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in @@ -0,0 +1,192 @@ +#!@TERMUX__PREFIX@/bin/bash +# shellcheck shell=bash + +## +# `libtermux_exec__nos__c__tests__main` +## +libtermux_exec__nos__c__tests__main() { + + TERMUX_EXEC__TESTS__LOG_TAG="lib@TERMUX__LNAME@-exec_c.tests" + + termux_exec__tests__log 4 "main()" + + # Set `TERMUX_EXEC__TESTS__TESTS_PATH` used by compiled c tests. + if [[ ! "$TERMUX_EXEC__TESTS__TESTS_PATH" =~ $TERMUX_EXEC__TESTS__REGEX__ROOTFS_OR_ABSOLUTE_PATH ]]; then + termux_exec__tests__log_error "The TERMUX_EXEC__TESTS__TESTS_PATH '$TERMUX_EXEC__TESTS__TESTS_PATH' is either not set or is not an absolute path" + return 1 + fi + + + # Run unit tests. + if [[ "$TERMUX_EXEC__TESTS__RUN_UNIT_TESTS" == "true" ]]; then + libtermux_exec__nos__c__unit_tests__run_command || return $? + fi + + # Run runtime tests. + if [[ "$TERMUX_EXEC__TESTS__RUN_RUNTIME_TESTS" == "true" ]]; then + libtermux_exec__nos__c__runtime_tests__run_command || return $? + fi + + return 0 + +} + + + +## +# `libtermux_exec__nos__c__unit_tests__run_command` +## +libtermux_exec__nos__c__unit_tests__run_command() { + + local return_value + + termux_exec__tests__log 4 "Running 'unit' tests" + + ( + libtermux_exec__nos__c__unit_binary_tests__run_command || exit $? + ) || return $? + + return 0 + +} + +## +# `libtermux_exec__nos__c__unit_binary_tests__run_command` +## +libtermux_exec__nos__c__unit_binary_tests__run_command() { + + local return_value + + local tests_start_time; tests_start_time="$(date "+%s")" || return $? + + local unit_binary_tests_variant="unit-binary-tests" + + if [[ "$TERMUX_EXEC__TESTS__USE_FSANITIZE_BUILDS" == "true" ]]; then + unit_binary_tests_variant+="-fsanitize" + else + unit_binary_tests_variant+="-nofsanitize" + fi + + termux_exec__tests__log 1 "Running 'libtermux-exec_nos_c_tre_${unit_binary_tests_variant}'" + + output="$( + printf -v "$TERMUX_EXEC__TESTS__LOG_LEVEL___N" "%s" "$TERMUX_EXEC__TESTS__LOG_LEVEL" || exit $? + export "${TERMUX_EXEC__TESTS__LOG_LEVEL___N?}" || exit $? + export LD_PRELOAD="$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH" || exit $? + ASAN_OPTIONS="fast_unwind_on_malloc=false:detect_leaks=$TERMUX_EXEC__TESTS__DETECT_LEAKS" LSAN_OPTIONS="report_objects=$TERMUX_EXEC__TESTS__DETECT_LEAKS" \ + "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/bin/libtermux-exec_nos_c_tre_${unit_binary_tests_variant}" 2>&1)" + return_value=$? + if [ $return_value -eq 0 ] || + { [ $return_value -eq 141 ] && + { [[ "$output" == *"WARNING: Can't read from symbolizer at fd"* ]] || + [[ "$output" == *"WARNING: external symbolizer didn't start up correctly!"* ]] + } && + [[ "$output" == *"runTests(end)"* ]]; + }; then + printf "%s\n" "$output" + else + printf "%s\n" "$output" 1>&2 + termux_exec__tests__log_error "'libtermux-exec_nos_c_tre_${unit_binary_tests_variant}' failed" + return $return_value + fi + + termux_exec__tests__log 2 "'libtermux-exec_nos_c_tre_${unit_binary_tests_variant}' completed in \ +$(termux_exec__tests__print_elapsed_time "$tests_start_time")" + + return 0 + +} + + + +## +# `libtermux_exec__nos__c__runtime_tests__run_command` +## +libtermux_exec__nos__c__runtime_tests__run_command() { + + local return_value + + termux_exec__tests__log 4 "Running 'runtime' tests" + + ( + libtermux_exec__nos__c__runtime_binary_tests__run_command || return $? || exit $? + ) || return $? + + ( + libtermux_exec__nos__c__runtime_script_tests__run_command || return $? || exit $? + ) || return $? + + return 0 + +} + +## +# `libtermux_exec__nos__c__runtime_binary_tests__run_command` +## +libtermux_exec__nos__c__runtime_binary_tests__run_command() { + + local return_value + + local tests_start_time; tests_start_time="$(date "+%s")" || return $? + + local runtime_binary_tests_variant="runtime-binary-tests" + + if [[ "$TERMUX_EXEC__TESTS__USE_FSANITIZE_BUILDS" == "true" ]]; then + runtime_binary_tests_variant+="-fsanitize" + else + runtime_binary_tests_variant+="-nofsanitize" + fi + + if [ "$ANDROID__BUILD_VERSION_SDK" -ge 28 ] && \ + [ -f "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/bin/libtermux-exec_nos_c_tre_${runtime_binary_tests_variant}28" ]; then + runtime_binary_tests_variant+="28" + fi + + termux_exec__tests__log 1 "Running 'libtermux-exec_nos_c_tre_${runtime_binary_tests_variant}'" + ( + printf -v "$TERMUX_EXEC__TESTS__LOG_LEVEL___N" "%s" "$TERMUX_EXEC__TESTS__LOG_LEVEL" || exit $? + export "${TERMUX_EXEC__TESTS__LOG_LEVEL___N?}" || exit $? + ASAN_OPTIONS="fast_unwind_on_malloc=false:detect_leaks=$TERMUX_EXEC__TESTS__DETECT_LEAKS" LSAN_OPTIONS="report_objects=$TERMUX_EXEC__TESTS__DETECT_LEAKS" \ + "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/bin/libtermux-exec_nos_c_tre_${runtime_binary_tests_variant}" + ) + return_value=$? + if [ $return_value -ne 0 ]; then + termux_exec__tests__log_error "'libtermux-exec_nos_c_tre_${runtime_binary_tests_variant}' failed" + return $return_value + fi + + termux_exec__tests__log 2 "'libtermux-exec_nos_c_tre_${runtime_binary_tests_variant}' completed in \ +$(termux_exec__tests__print_elapsed_time "$tests_start_time")" + +} + +## +# `libtermux_exec__nos__c__runtime_script_tests__run_command` +## +libtermux_exec__nos__c__runtime_script_tests__run_command() { + + local return_value + + local tests_start_time; tests_start_time="$(date "+%s")" || return $? + + termux_exec__tests__log 1 "Running 'libtermux-exec_nos_c_tre_runtime-script-tests'" + + ( + # shellcheck source=lib/termux-exec_nos_c_tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in + termux_exec__tests__source_file_from_path \ + "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/scripts/libtermux-exec_nos_c_tre_runtime-script-tests" || exit $? + + libtermux_exec__nos__c__runtime_script_tests__main + ) + return_value=$? + if [ $return_value -ne 0 ]; then + termux_exec__tests__log_error "'libtermux-exec_nos_c_tre_runtime-script-tests' failed" + return $return_value + fi + + termux_exec__tests__log 2 "'libtermux-exec_nos_c_tre_runtime-script-tests' completed in \ +$(termux_exec__tests__print_elapsed_time "$tests_start_time")" + + return 0 + +} diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in b/lib/termux-exec_nos_c_tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in new file mode 100644 index 0000000..2489435 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in @@ -0,0 +1,43 @@ +#!@TERMUX__PREFIX@/bin/bash +# shellcheck shell=bash + +## +# `libtermux_exec__nos__c__runtime_script_tests__main` +## +libtermux_exec__nos__c__runtime_script_tests__main() { + + TERMUX_EXEC__TESTS__LOG_TAG="lib@TERMUX__LNAME@-exec_c.rs-tests" + + termux_exec__tests__log 4 "main()" + + + # Run tests. + libtermux_exec__nos__c__runtime_script_tests__run_tests || return $? + + return 0 +} + +## +# `libtermux_exec__nos__c__runtime_script_tests__run_tests` +## +libtermux_exec__nos__c__runtime_script_tests__run_tests() { + + termux_exec__tests__log 2 "runTests(start)" + + TERMUX_EXEC__TESTS__TESTS_COUNT="0" + TERMUX_EXEC__TESTS__LOG_EMPTY_LINE_AFTER_SCRIPT_TEST="true" + + + + ( + # shellcheck source=lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/exec-intercept_runtime-script-tests.in + termux_exec__tests__source_file_from_path \ + "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/exec-intercept_runtime-script-tests" || exit $? + ExecIntercept_runTests || exit $? + ) || return $? + + + + termux_exec__tests__log 2 "runTests(end): $TERMUX_EXEC__TESTS__TESTS_COUNT tests completed" + +} diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/exec-intercept_runtime-script-tests.in b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/exec-intercept_runtime-script-tests.in new file mode 100644 index 0000000..161a305 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/exec-intercept_runtime-script-tests.in @@ -0,0 +1,420 @@ +#!@TERMUX__PREFIX@/bin/bash +# shellcheck shell=bash + +## +# `ExecIntercept_runTests` +## +ExecIntercept_runTests() { + + termux_exec__tests__log 2 "ExecIntercept_runTests()" + + testExecIntercept || return $? + + return 0 +} + + + + + +testExecIntercept() { + + termux_exec__tests__log 3 "testExecIntercept()" + + testExecIntercept__Basic || return $? + testExecIntercept__Interpreter || return $? + testExecIntercept__SingleAndDoubleDotExecutablePaths || return $? + testExecIntercept__SingleAndDoubleDotInterpreterPaths || return $? + testExecIntercept__Shell || return $? + + return 0 + +} + +testExecIntercept__Basic() { + + termux_exec__tests__log 4 "testExecIntercept__Basic()" + + termux_exec__tests__run_script_test "not-executable" \ + --no-test-file-set-executable \ + "#!/bin/bash${NL}echo hello" \ + 126 "^.*: $TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH: Permission denied$" || return $? + + termux_exec__tests__run_script_test "is-executable" \ + "#!/bin/bash${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "usr-bin-env" \ + "#!/usr/bin/env bash${NL}echo hello-user-bin-env" \ + 0 "^hello-user-bin-env$" || return $? + + termux_exec__tests__run_script_test "termux-bin-env" \ + "#!$TERMUX__PREFIX/bin/env bash${NL}echo hello-termux-bin-env" \ + 0 "^hello-termux-bin-env$" || return $? + + termux_exec__tests__run_script_test "empty-file" \ + "" \ + 0 "^$" || return $? + + return 0 + +} + +testExecIntercept__Interpreter() { + + termux_exec__tests__log 4 "testExecIntercept__Interpreter()" + + # `termux-exec` will return with the `Not an ELF or no shebang in executable path (ENOEXEC)` + # error, but bash will manually execute the script. + BASH_VERSION="" termux_exec__tests__run_script_test "shebang-with-pre-!-whitespace" \ + "# !/bin/sh${NL}echo \"\$BASH_VERSION\"" \ + 0 "^$(termux_exec__tests__escape_string_for_regex "$BASH_VERSION")$" || return $? + + termux_exec__tests__run_script_test "shebang-with-pre-path-whitespace" \ + "#! /bin/sh${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "shebang-with-args-with-spaces" \ + "#!/bin/echo hello world bye${NL}" \ + 0 "^hello world bye $TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH arg1 arg2$" \ + "arg1" "arg2" || return $? + + + termux_exec__tests__run_script_test "shebang-path-missing" \ + "#!${NL}" \ + 0 "^$" || return $? + + termux_exec__tests__run_script_test "shebang-path-whitespace" \ + "#! ${NL}" \ + 0 "^$" || return $? + + termux_exec__tests__run_script_test "shebang-path-rootfs" \ + "#!/${NL}" \ + 126 "^.*: $TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH: /: bad interpreter: Permission denied$" || return $? + + if [ "$ANDROID__BUILD_VERSION_SDK" -ge 24 ]; then + termux_exec__tests__run_script_test "shebang-path-not-found" \ + "#!/x${NL}" \ + 127 "^.*: $TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH: cannot execute: required file not found$" || return $? + else + termux_exec__tests__run_script_test "shebang-path-not-found" \ + "#!/x${NL}" \ + 126 "^.*: $TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH: /x: bad interpreter: No such file or directory$" || return $? + fi + + return 0 + +} + +testExecIntercept__SingleAndDoubleDotExecutablePaths() { + + termux_exec__tests__log 4 "testExecIntercept__SingleAndDoubleDotExecutablePaths()" + + # $TMPDIR + # - termux-exec + # - exec + # - dir1 + # - subdir1 + # - dir2 + + local tests_dir_path="$TERMUX_EXEC__TESTS__EXEC_TMPDIR_PATH" + rm -rf "$tests_dir_path" || return $? + + local dir1_name="dir1" + local dir1_path="$tests_dir_path/$dir1_name" + local subdir1_name="subdir1" + local subdir1_path="$dir1_path/$subdir1_name" + mkdir -p "$subdir1_path" || return $? + + local dir2_name="dir2" + local dir2_path="$tests_dir_path/$dir2_name" + mkdir -p "$dir2_path" || return $? + + local original_script_test_file_path="$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH" + + + + # Relative: Executable in current directory. + TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH="$tests_dir_path/$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" + + termux_exec__tests__run_script_test "executable-relative-current-dir-one-single-dot" \ + --executable-path="./$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" --working-dir="$tests_dir_path" \ + "#!/bin/bash${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "executable-relative-current-dir-two-single-dot" \ + --executable-path="././$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" --working-dir="$tests_dir_path" \ + "#!/bin/bash${NL}echo hello" \ + 0 "^hello$" || return $? + + + # Relative: Executable in parent directory. + TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH="$tests_dir_path/$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" + + termux_exec__tests__run_script_test "executable-relative-parent-dir-one-double-dot" \ + --executable-path="../$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" --working-dir="$dir1_path" \ + "#!/bin/bash${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "executable-relative-parent-dir-two-double-dot" \ + --executable-path="../../$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" --working-dir="$subdir1_path" \ + "#!/bin/bash${NL}echo hello" \ + 0 "^hello$" || return $? + + + # Relative: Executable in sibling directory. + TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH="$dir2_path/$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" + + termux_exec__tests__run_script_test "executable-relative-sibling-dir-one-double-dot" \ + --executable-path="../$dir2_name/$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" --working-dir="$dir1_path" \ + "#!/bin/bash${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "executable-relative-sibling-dir-two-double-dot" \ + --executable-path="../../$dir2_name/$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" --working-dir="$subdir1_path" \ + "#!/bin/bash${NL}echo hello" \ + 0 "^hello$" || return $? + + + + # Absolute: Executable in current directory. + TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH="$tests_dir_path/$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" + + termux_exec__tests__run_script_test "executable-absolute-current-dir-one-single-dot" \ + --executable-path="$tests_dir_path/./$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" \ + "#!/bin/bash${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "executable-absolute-current-dir-two-single-dot" \ + --executable-path="$tests_dir_path/././$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" \ + "#!/bin/bash${NL}echo hello" \ + 0 "^hello$" || return $? + + + # Absolute: Executable in parent directory. + TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH="$tests_dir_path/$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" + + termux_exec__tests__run_script_test "executable-absolute-parent-dir-one-double-dot" \ + --executable-path="$dir1_path/../$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" \ + "#!/bin/bash${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "executable-absolute-parent-dir-two-double-dot" \ + --executable-path="$subdir1_path/../../$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" \ + "#!/bin/bash${NL}echo hello" \ + 0 "^hello$" || return $? + + + # Absolute: Executable in sibling directory. + TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH="$dir2_path/$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" + + termux_exec__tests__run_script_test "executable-absolute-sibling-dir-one-double-dot" \ + --executable-path="$dir1_path/../$dir2_name/$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" \ + "#!/bin/bash${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "executable-absolute-sibling-dir-two-double-dot" \ + --executable-path="$subdir1_path/../../$dir2_name/$TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_NAME" \ + "#!/bin/bash${NL}echo hello" \ + 0 "^hello$" || return $? + + + + TERMUX_EXEC__TESTS__SCRIPT_TEST_FILE_PATH="$original_script_test_file_path" + + return 0 + +} + +testExecIntercept__SingleAndDoubleDotInterpreterPaths() { + + termux_exec__tests__log 4 "testExecIntercept__SingleAndDoubleDotInterpreterPaths()" + + # $TMPDIR + # - termux-exec + # - exec + # - dir1 + # - subdir1 + # - dir2 + + local tests_dir_path="$TERMUX_EXEC__TESTS__EXEC_TMPDIR_PATH" + rm -rf "$tests_dir_path" || return $? + + local dir1_name="dir1" + local dir1_path="$tests_dir_path/$dir1_name" + local subdir1_name="subdir1" + local subdir1_path="$dir1_path/$subdir1_name" + mkdir -p "$subdir1_path" || return $? + + local dir2_name="dir2" + local dir2_path="$tests_dir_path/$dir2_name" + mkdir -p "$dir2_path" || return $? + + + local bash_bin_path="$TERMUX__PREFIX/bin/bash" + local interpreter_file_name="bash" + local interpreter_file_path + + + # Relative: Executable in current directory. + interpreter_file_path="$tests_dir_path/$interpreter_file_name" + ln -s "$bash_bin_path" "$interpreter_file_path" || return $? + + termux_exec__tests__run_script_test "interpreter-relative-current-dir-one-single-dot" \ + --working-dir="$tests_dir_path" \ + "#!./$interpreter_file_name${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "interpreter-relative-current-dir-two-single-dot" \ + --working-dir="$tests_dir_path" \ + "#!././$interpreter_file_name${NL}echo hello" \ + 0 "^hello$" || return $? + + rm -f "$interpreter_file_path" || return $? + + + # Relative: Executable in parent directory. + interpreter_file_path="$tests_dir_path/$interpreter_file_name" + ln -s "$bash_bin_path" "$interpreter_file_path" || return $? + + termux_exec__tests__run_script_test "interpreter-relative-parent-dir-one-double-dot" \ + --working-dir="$dir1_path" \ + "#!../$interpreter_file_name${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "interpreter-relative-parent-dir-two-double-dot" \ + --working-dir="$subdir1_path" \ + "#!../../$interpreter_file_name${NL}echo hello" \ + 0 "^hello$" || return $? + + rm -f "$interpreter_file_path" || return $? + + + # Relative: Executable in sibling directory. + interpreter_file_path="$dir2_path/$interpreter_file_name" + ln -s "$bash_bin_path" "$interpreter_file_path" || return $? + + termux_exec__tests__run_script_test "interpreter-relative-sibling-dir-one-double-dot" \ + --working-dir="$dir1_path" \ + "#!../$dir2_name/$interpreter_file_name${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "interpreter-relative-sibling-dir-two-double-dot" \ + --working-dir="$subdir1_path" \ + "#!../../$dir2_name/$interpreter_file_name${NL}echo hello" \ + 0 "^hello$" || return $? + + rm -f "$interpreter_file_path" || return $? + + + + # Absolute: Executable in current directory. + interpreter_file_path="$tests_dir_path/$interpreter_file_name" + ln -s "$bash_bin_path" "$interpreter_file_path" || return $? + + termux_exec__tests__run_script_test "interpreter-absolute-current-dir-one-single-dot" \ + "#!$tests_dir_path/./$interpreter_file_name${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "interpreter-absolute-current-dir-two-single-dot" \ + "#!$tests_dir_path/././$interpreter_file_name${NL}echo hello" \ + 0 "^hello$" || return $? + + rm -f "$interpreter_file_path" || return $? + + + # Absolute: Executable in parent directory. + interpreter_file_path="$tests_dir_path/$interpreter_file_name" + ln -s "$bash_bin_path" "$interpreter_file_path" || return $? + + termux_exec__tests__run_script_test "interpreter-absolute-parent-dir-one-double-dot" \ + "#!$dir1_path/../$interpreter_file_name${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "interpreter-absolute-parent-dir-two-double-dot" \ + "#!$subdir1_path/../../$interpreter_file_name${NL}echo hello" \ + 0 "^hello$" || return $? + + rm -f "$interpreter_file_path" || return $? + + + # Absolute: Executable in sibling directory. + interpreter_file_path="$dir2_path/$interpreter_file_name" + ln -s "$bash_bin_path" "$interpreter_file_path" || return $? + + termux_exec__tests__run_script_test "interpreter-absolute-sibling-dir-one-double-dot" \ + "#!$dir1_path/../$dir2_name/$interpreter_file_name${NL}echo hello" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "interpreter-absolute-sibling-dir-two-double-dot" \ + "#!$subdir1_path/../../$dir2_name/$interpreter_file_name${NL}echo hello" \ + 0 "^hello$" || return $? + + rm -f "$interpreter_file_path" || return $? + + return 0 + +} + +testExecIntercept__Shell() { + + termux_exec__tests__log 4 "testExecIntercept__Shell()" + + # `/dev/stdin` does not exist on Android 7, so use `/proc/self/fd/0` + + termux_exec__tests__run_script_test "bash-heredoc-no-args" \ + "#!/usr/bin/bash${NL}bash <<'EOF'${NL}echo hello${NL}EOF" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "cat-bash-heredoc-no-args" \ + "#!/usr/bin/bash${NL}cat <<'EOF' | bash${NL}echo hello${NL}EOF" \ + 0 "^hello$" || return $? + + + termux_exec__tests__run_script_test "bash-heredoc-with-args" \ + "#!/usr/bin/bash${NL}bash /proc/self/fd/0 hello<<'EOF'${NL}echo \$1${NL}EOF" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "cat-bash-heredoc-with-args" \ + "#!/usr/bin/bash${NL}cat <<'EOF' | bash /proc/self/fd/0 hello${NL}echo \$1${NL}EOF" \ + 0 "^hello$" || return $? + + + termux_exec__tests__run_script_test "bash-herestring-no-args" \ + "#!/usr/bin/bash${NL}bash <<<'echo hello'" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "cat-bash-herestring-no-args" \ + "#!/usr/bin/bash${NL}cat <<<'echo hello' | bash" \ + 0 "^hello$" || return $? + + + termux_exec__tests__run_script_test "bash-herestring-with-args" \ + "#!/usr/bin/bash${NL}bash /proc/self/fd/0 hello <<<'echo \$1'" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "cat-bash-herestring-with-args" \ + "#!/usr/bin/bash${NL}cat <<<'echo \$1' | bash /proc/self/fd/0 hello" \ + 0 "^hello$" || return $? + + + termux_exec__tests__run_script_test "builtin-echo-cat-pipe" \ + "#!/usr/bin/bash${NL}echo hello | cat" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "external-echo-cat-pipe" \ + "#!/usr/bin/bash${NL}$TERMUX__PREFIX/bin/echo hello | cat" \ + 0 "^hello$" || return $? + + termux_exec__tests__run_script_test "external-echo-sed-pipe" \ + "#!/usr/bin/bash${NL}$TERMUX__PREFIX/bin/echo '|hello|' | sed -e 's/|//g'" \ + 0 "^hello$" || return $? + + + termux_exec__tests__run_script_test "fd-read-write" \ + "#!/usr/bin/bash${NL}exec {fd}< <(echo -n hello)${NL}cat /proc/self/fd/\${fd}${NL}exec {fd}>&-" \ + 0 "^hello$" || return $? + + return 0 + +} diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary.c b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary.c new file mode 100644 index 0000000..ee55833 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary.c @@ -0,0 +1,11 @@ +#include +#include + +int main(int argc, char* argv[]) { + for (int i = 1; i < argc; i++) { + fprintf(stdout, i == 1 ? "%s" : " %s", argv[i]); + } + + fprintf(stdout, "\n"); + fflush(stdout); +} diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary.sym b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary.sym new file mode 120000 index 0000000..f2573d9 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary.sym @@ -0,0 +1 @@ +./print-args-binary \ No newline at end of file diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh new file mode 100644 index 0000000..da47567 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh @@ -0,0 +1,3 @@ +#!/usr/bin/sh + +echo "$@" diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh.sym b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh.sym new file mode 120000 index 0000000..258f6f5 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh.sym @@ -0,0 +1 @@ +./print-args-linux-script.sh \ No newline at end of file diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-termux-script.sh.in b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-termux-script.sh.in new file mode 100644 index 0000000..9fd920a --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-termux-script.sh.in @@ -0,0 +1,3 @@ +#!@TERMUX__PREFIX@/bin/sh + +echo "$@" diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-termux-script.sh.sym b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-termux-script.sh.sym new file mode 120000 index 0000000..52bdef9 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-termux-script.sh.sym @@ -0,0 +1 @@ +./print-args-termux-script.sh \ No newline at end of file diff --git a/lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c b/lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c new file mode 100644 index 0000000..3e18ce4 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c @@ -0,0 +1,119 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#ifdef __ANDROID__ +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + + +static const char* LOG_TAG = "rb-tests"; + +static uid_t UID; + +#define TERMUX_EXEC__TESTS__TESTS_PATH TERMUX__PREFIX "/libexec/installed-tests/termux-exec" + +extern char **environ; + + +static void init(); +static void initLogger(); +static void initChild(ForkInfo *info); +static void runTests(); + + + +#include "termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c" + + + +__attribute__((visibility("default"))) +int main() { + init(); + + logVVerbose(LOG_TAG, "main()"); + + runTests(); + + return 0; +} + + + +static void init() { + errno = 0; + + UID = geteuid(); + + libtermux_core__nos__c__setIsRunningTests(true); + libtermux_exec__nos__c__setIsRunningTests(true); + + initLogger(); +} + +static void initLogger() { + setDefaultLogTagAndPrefix("lib" TERMUX__LNAME "-exec_c"); + setCurrentLogLevel(termuxExec_tests_logLevel_get()); + setLogFormatMode(LOG_FORMAT_MODE__TAG_AND_MESSAGE); +} + +static void initChild(ForkInfo *info) { + (void)info; + initLogger(); +} + + + +void runTests() { + + logDebug(LOG_TAG, "runTests(start)"); + + + char termuxExec_tests_primaryLDPreloadFilePathBuffer[PATH_MAX]; + int result = getPathFromEnv(LOG_LEVEL__NORMAL, LOG_TAG, + "primary_ld_preload_file_path", ENV__TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH, + true, 0, true, true, + termuxExec_tests_primaryLDPreloadFilePathBuffer, sizeof(termuxExec_tests_primaryLDPreloadFilePathBuffer)); + if (result != 0 || strlen(termuxExec_tests_primaryLDPreloadFilePathBuffer) < 1) { + exit(1); + } + const char* termuxExec_tests_primaryLDPreloadFilePath = termuxExec_tests_primaryLDPreloadFilePathBuffer; + logErrorVVerbose(LOG_TAG, "primary_ld_preload_file_path: '%s'", termuxExec_tests_primaryLDPreloadFilePath); + + + ExecIntercept_runTests(); + + + if (stringEndsWith(termuxExec_tests_primaryLDPreloadFilePath, "/libtermux-exec-linker-ld-preload.so")) { + logVerbose(LOG_TAG, "LinkerLDPreload_runTests()"); + } + + + logDebug(LOG_TAG, "runTests(end)"); + +} diff --git a/lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c b/lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c new file mode 100644 index 0000000..7c9823d --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c @@ -0,0 +1,80 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + + +static const char* LOG_TAG = "ub-tests"; + + +static void init(); +static void initLogger(); +static void runTests(); + + + +#include "termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c" + + + +__attribute__((visibility("default"))) +int main() { + init(); + + logVVerbose(LOG_TAG, "main()"); + + runTests(); + + return 0; +} + + + +static void init() { + errno = 0; + + libtermux_core__nos__c__setIsRunningTests(true); + libtermux_exec__nos__c__setIsRunningTests(true); + + initLogger(); +} + +static void initLogger() { + setDefaultLogTagAndPrefix("lib" TERMUX__LNAME "-exec_c"); + setCurrentLogLevel(termuxExec_tests_logLevel_get()); + setLogFormatMode(LOG_FORMAT_MODE__TAG_AND_MESSAGE); +} + + + +void runTests() { + + logDebug(LOG_TAG, "runTests(start)"); + + ExecIntercept_runTests(); + + logDebug(LOG_TAG, "runTests(end)"); + +} diff --git a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c new file mode 100644 index 0000000..c19cd63 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c @@ -0,0 +1,470 @@ +#include + +#include + + + +void test__execIntercept(); + + + +void ExecIntercept_runTests() { + logVerbose(LOG_TAG, "ExecIntercept_runTests()"); + + test__execIntercept(); + + int__AEqual(0, errno); +} + + + + + +#if defined __ANDROID__ && __ANDROID_API__ >= 28 +#define FEXECVE_SUPPORTED 1 +#endif + +#ifndef __ANDROID__ +#define FEXECVE_SUPPORTED 1 +#endif + +#if defined FEXECVE_SUPPORTED +#define FEXECVE_CALL_IMPL() \ +int fexecveCall(int fd, char *const *argv, char *const *envp) { \ + return fexecve(fd, argv, envp); \ +} +#else +#define FEXECVE_SUPPORTED 0 +#define FEXECVE_CALL_IMPL() \ +int fexecveCall(int fd, char *const *argv, char *const *envp) { \ + (void)fd; (void)argv; (void)envp; \ + logStrerror(LOG_TAG, "fexecve not supported on __ANDROID_API__ %d and requires api level >= %d", __ANDROID_API__, 28); \ + return -1; \ +} +#endif + +FEXECVE_CALL_IMPL() +#undef FEXECVE_CALL_IMPL + + + +#define execWrapper(variant, name, envp, ...) \ + if (1) { \ + /* Construct argv */ \ + char *argv[] = {__VA_ARGS__}; \ + \ + switch (variant) { \ + case ExecVE: { \ + actualReturnValue = execve(name, argv, envp); \ + break; \ + } case ExecL: { \ + actualReturnValue = execl(name, __VA_ARGS__); \ + break; \ + } case ExecLP: { \ + actualReturnValue = execlp(name, __VA_ARGS__); \ + break; \ + } case ExecLE: { \ + actualReturnValue = execle(name, __VA_ARGS__, envp); \ + break; \ + } case ExecV: { \ + actualReturnValue = execv(name, argv); \ + break; \ + } case ExecVP: { \ + actualReturnValue = execvp(name, argv); \ + break; \ + } case ExecVPE: { \ + actualReturnValue = execvpe(name, argv, envp); \ + break; \ + } case FExecVE: { \ + int fd = open(name, 0); \ + if (fd == -1) { \ + logStrerror(LOG_TAG, "open() call failed"); \ + exit(1); \ + } \ + \ + actualReturnValue = fexecveCall(fd, argv, envp); \ + close(fd); \ + break; \ + } default: { \ + logStrerror(LOG_TAG, "Unknown exec() variant %d", variant); \ + exit(1); \ + } \ + } \ + \ + } else ((void)0) + +#define runExecTest(testName, \ + expectedReturnValue, expectedErrno, \ + expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + variant, name, envp, ...) \ + if (1) { \ + logVVVerbose(LOG_TAG, "%s_exec_%s()", testName, EXEC_VARIANTS_STR[variant]); \ + \ + INIT_FORK_INFO(info); \ + info.parentLogTag = LOG_TAG; \ + info.childLogTag = LOG_TAG; \ + info.onChildFork = initChild; \ + int result = forkChild(&info); \ + if (result != 0) { \ + logError(LOG_TAG, "Unexpected return value for forkChild '%d'", result); \ + exit(1); \ + } \ + \ + if (info.isChild) { \ + int actualReturnValue; \ + execWrapper(variant, name, envp, __VA_ARGS__); \ + int actualErrno = errno; \ + int testFailed = 0; \ + if (actualReturnValue != expectedReturnValue) { \ + logError(LOG_TAG, "FAILED: '%s' '%s()' test", testName, EXEC_VARIANTS_STR[variant]); \ + logError(LOG_TAG, "Expected return_value does not equal actual return_value"); \ + testFailed=1; \ + } else if (actualErrno != expectedErrno) { \ + logError(LOG_TAG, "FAILED: '%s' '%s()' test", testName, EXEC_VARIANTS_STR[variant]); \ + logError(LOG_TAG, "Expected errno does not equal actual errno"); \ + testFailed=1; \ + } \ + \ + if (testFailed == 1) { \ + logError(LOG_TAG, "actual_return_value: '%d'", actualReturnValue); \ + logError(LOG_TAG, "expected_return_value: '%d'", expectedReturnValue); \ + logError(LOG_TAG, "actual_errno: '%d'", actualErrno); \ + logError(LOG_TAG, "expected_errno: '%d'", expectedErrno); \ + exitForkWithError(&info, 100); \ + } else { \ + exit(0); \ + } \ + } else { \ + if (WIFEXITED(info.status)) { \ + ; \ + } else if (WIFSIGNALED(info.status)) { \ + logInfo(LOG_TAG, "Killed by signal %d\n", WTERMSIG(info.status)); \ + } else if (WIFSTOPPED(info.status)) { \ + logInfo(LOG_TAG, "Stopped by signal %d\n", WSTOPSIG(info.status)); \ + } else if (WIFCONTINUED(info.status)) { \ + logInfo(LOG_TAG, "Continued"); \ + } else { \ + logInfo(LOG_TAG, "CANCELLED"); \ + exit(2); \ + } \ + \ + int actualExitCode = info.exitCode; \ + int testFailed = 0; \ + int regexMatchResult = 1; \ + if (expectedOutputRegex != NULL && \ + (regexMatchResult = regexMatch(info.output, expectedOutputRegex, expectedOutputRegexFlags)) != 0) { \ + logError(LOG_TAG, "FAILED: '%s' '%s()' test", testName, EXEC_VARIANTS_STR[variant]); \ + logError(LOG_TAG, "Expected output_regex does not equal match actual output"); \ + testFailed=1; \ + } else if (actualExitCode != expectedExitCode) { \ + logError(LOG_TAG, "FAILED: '%s' '%s()' test", testName, EXEC_VARIANTS_STR[variant]); \ + logError(LOG_TAG, "Expected exit_code does not equal actual exit_code"); \ + testFailed=1; \ + } \ + \ + if (testFailed == 1) { \ + logError(LOG_TAG, "actual_exit_code: '%d'", actualExitCode); \ + logError(LOG_TAG, "expected_exit_code: '%d'", expectedExitCode); \ + logError(LOG_TAG, "actual_output: '%s'", info.output); \ + logError(LOG_TAG, "expected_output_regex: '%s' (%d)", expectedOutputRegex, expectedOutputRegexFlags); \ + if (regexMatchResult != 1) { \ + logError(LOG_TAG, "regexMatchResult: '%d'", regexMatchResult); \ + } \ + exitForkWithError(&info, 100); \ + } else { \ + /* logDebug(LOG_TAG, "PASSED"); */ \ + free(info.output); \ + errno = 0; \ + } \ + } \ + \ + } else ((void)0) + +#define runAllExecWrappersTest(testName, \ + expectedReturnValue, expectedErrno, \ + expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + file, path, envp, ...) \ + if (1) { \ + \ + logVVerbose(LOG_TAG, "%s_exec()", testName); \ + \ + { \ + /* ExecVE */ \ + runExecTest(testName, expectedReturnValue, expectedErrno, \ + expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + ExecVE, path, envp, path, __VA_ARGS__); \ + } \ + \ + \ + { \ + /* ExecL */ \ + runExecTest(testName, expectedReturnValue, expectedErrno, \ + expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + ExecL, path, NULL, path, __VA_ARGS__); \ + } \ + { \ + /* ExecLP */ \ + runExecTest(testName, expectedReturnValue, expectedErrno, \ + expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + ExecLP, file, NULL, file, __VA_ARGS__); \ + } \ + { \ + /* ExecLE */ \ + runExecTest(testName, expectedReturnValue, expectedErrno, \ + expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + ExecLE, path, envp, path, __VA_ARGS__); \ + } \ + \ + \ + { \ + /* ExecV */ \ + runExecTest(testName, expectedReturnValue, expectedErrno, \ + expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + ExecV, path, NULL, path, __VA_ARGS__); \ + } \ + { \ + /* ExecVP */ \ + runExecTest(testName, expectedReturnValue, expectedErrno, \ + expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + ExecVP, file, NULL, file, __VA_ARGS__); \ + } \ + { \ + /* ExecVPE */ \ + runExecTest(testName, expectedReturnValue, expectedErrno, \ + expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + ExecVPE, file, envp, file, __VA_ARGS__); \ + } \ + \ + \ + { \ + if (FEXECVE_SUPPORTED == 1) { \ + /* FExecVE */ \ + runExecTest(testName, expectedReturnValue, expectedErrno, \ + expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + FExecVE, path, envp, path, __VA_ARGS__); \ + } \ + } \ + \ + } else ((void)0) + + +#define asprintf_wrapper(strp, fmt, ...) \ + if (1) { \ + if (asprintf(strp, fmt, __VA_ARGS__) == -1) { \ + errno = ENOMEM; \ + logStrerrorDebug(LOG_TAG, "asprintf failed for new '%s'", fmt, __VA_ARGS__); \ + exit(1); \ + } \ + } else ((void)0) + + + + +void test__execIntercept__Basic(); +void test__execIntercept__Files(const char* termuxExec_tests_testsPath, const char* currentPath, char* envCurrentPath); +void test__execIntercept__PackageManager(); + +void test__execIntercept() { + logVerbose(LOG_TAG, "test__execIntercept()"); + + + char termuxExec_tests_testsPathBuffer[PATH_MAX]; + const char* termuxExec_tests_testsPath; + int result = getPathFromEnv(LOG_LEVEL__NORMAL, LOG_TAG, + "tests_path", ENV__TERMUX_EXEC__TESTS__TESTS_PATH, + true, 0, true, true, + termuxExec_tests_testsPathBuffer, sizeof(termuxExec_tests_testsPathBuffer)); + if (result < 0) { + exit(1); + } else if (result == 1 || strlen(termuxExec_tests_testsPathBuffer) < 1) { + termuxExec_tests_testsPath = TERMUX_EXEC__TESTS__TESTS_PATH; + } else { + termuxExec_tests_testsPath = termuxExec_tests_testsPathBuffer; + } + logErrorVVerbose(LOG_TAG, "tests_path: '%s'", termuxExec_tests_testsPath); + + + const char* currentPath = getenv(ENV__PATH); + char* envCurrentPath = NULL; + + if (currentPath == NULL || strlen(currentPath) < 1) { + envCurrentPath = ENV_PREFIX__PATH; + } else { + if (asprintf(&envCurrentPath, "%s%s", ENV_PREFIX__PATH, currentPath) == -1) { + errno = ENOMEM; + logStrerrorDebug(LOG_TAG, "asprintf failed for current '%s%s'", ENV_PREFIX__PATH, currentPath); + exit(1); + } + } + + + // TODO: Port tests from bionic. + // - https://cs.android.com/android/_/android/platform/bionic/+/refs/tags/android-14.0.0_r18:tests/unistd_test.cpp;l=1364 + // - https://cs.android.com/android/platform/superproject/+/android-14.0.0_r18:bionic/tests/utils.h;l=200 + + test__execIntercept__Basic(); + test__execIntercept__Files(termuxExec_tests_testsPath, currentPath, envCurrentPath); + test__execIntercept__PackageManager(); + + int__AEqual(0, errno); + + // We cannot free this in a test function that sets it as later test cases will use it. + free(envCurrentPath); +} + + + +void test__execIntercept__Basic() { + logVVerbose(LOG_TAG, "test__execIntercept__Basic()"); + + runAllExecWrappersTest("rootfs", + -1, EISDIR, + 0, NULL, 0, + "../../", TERMUX__ROOTFS, environ, + NULL); +} + +void test__execIntercept__Files(const char* termuxExec_tests_testsPath, const char* currentPath, char* envCurrentPath) { + logVVerbose(LOG_TAG, "test__execIntercept__Files()"); + + + + char* termuxExec__execTestFilesPath = NULL; + asprintf_wrapper(&termuxExec__execTestFilesPath, "%s/%s", + termuxExec_tests_testsPath, "lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files"); + + + // execlp(), execvp() and execvpe() search for file to be executed in $PATH, + // so set it with test exec files directory appended at end. + char* envNewPath = NULL; + + if (currentPath == NULL || strlen(currentPath) < 1) { + if (asprintf(&envNewPath, "%s%s", ENV_PREFIX__PATH, termuxExec__execTestFilesPath) == -1) { + errno = ENOMEM; + logStrerrorDebug(LOG_TAG, "asprintf failed for new '%s%s'", ENV_PREFIX__PATH, termuxExec__execTestFilesPath); + exit(1); + } + } else { + if (asprintf(&envNewPath, "%s%s:%s", ENV_PREFIX__PATH, currentPath, termuxExec__execTestFilesPath) == -1) { + errno = ENOMEM; + logStrerrorDebug(LOG_TAG, "asprintf failed for new '%s%s:%s'", ENV_PREFIX__PATH, currentPath, termuxExec__execTestFilesPath); + exit(1); + } + } + + + putenv(envNewPath); + + + char* testFilePath = NULL; + + asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-binary"); + runAllExecWrappersTest("print-args-binary", + 0, 0, + 0, "^goodbye-world$", REG_EXTENDED, + "print-args-binary", testFilePath, environ, + "goodbye-world", NULL); + free(testFilePath); + + asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-binary.sym"); + runAllExecWrappersTest("print-args-binary.sym", + 0, 0, + 0, "^goodbye-world$", REG_EXTENDED, + "print-args-binary.sym", testFilePath, environ, + "goodbye-world", NULL); + free(testFilePath); + + + asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-linux-script.sh"); + runAllExecWrappersTest("print-args-linux-script.sh", + 0, 0, + 0, "^goodbye-world$", REG_EXTENDED, + "print-args-linux-script.sh", testFilePath, environ, + "goodbye-world", NULL); + free(testFilePath); + + asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-linux-script.sh.sym"); + runAllExecWrappersTest("print-args-linux-script.sh.sym", + 0, 0, + 0, "^goodbye-world$", REG_EXTENDED, + "print-args-linux-script.sh.sym", testFilePath, environ, + "goodbye-world", NULL); + free(testFilePath); + + + asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-termux-script.sh"); + runAllExecWrappersTest("print-args-termux-script.sh", + 0, 0, + 0, "^goodbye-world$", REG_EXTENDED, + "print-args-termux-script.sh", testFilePath, environ, + "goodbye-world", NULL); + free(testFilePath); + + asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-termux-script.sh.sym"); + runAllExecWrappersTest("print-args-termux-script.sh.sym", + 0, 0, + 0, "^goodbye-world$", REG_EXTENDED, + "print-args-termux-script.sh.sym", testFilePath, environ, + "goodbye-world", NULL); + free(testFilePath); + + + putenv(envCurrentPath); + + + free(termuxExec__execTestFilesPath); + free(envNewPath); + +} + +void test__execIntercept__PackageManager() { + logVVerbose(LOG_TAG, "test__execIntercept__PackageManager()"); + + if (UID == 0) { + logStrerrorVerbose(LOG_TAG, "Not running 'package-manager' 'exec()' wrapper tests since running as root"); + return; + } + + char* termuxPackageManager = getenv(ENV__TERMUX_ROOTFS__PACKAGE_MANAGER); + if (termuxPackageManager == NULL || strlen(termuxPackageManager) < 1) { + logStrerrorVerbose(LOG_TAG, "Not running 'package-manager' 'exec()' wrapper tests since '%s' environment variable not set", + ENV__TERMUX_ROOTFS__PACKAGE_MANAGER); + return; + } + + char* termuxPackageManagerPath = NULL; + asprintf_wrapper(&termuxPackageManagerPath, "%s/bin/%s", TERMUX__PREFIX, termuxPackageManager); + + // In case bootstrap was built without a package manager. + if (access(termuxPackageManagerPath, X_OK) != 0) { + logStrerrorVerbose(LOG_TAG, "Not running 'package-manager' 'exec()' wrapper tests since failed to access package manager executable path '%s'", + termuxPackageManagerPath); + free(termuxPackageManagerPath); + errno = 0; + return; + } + + + + // apt: `apt x.x.x ()` + // pacman: `Pacman vx.x.x` Also can icon and license info + char* termuxPackageManagerVersionRegex = NULL; + if (asprintf(&termuxPackageManagerVersionRegex, "^.*%s v?[0-9][.][0-9][.][0-9].*$", termuxPackageManager) == -1) { + errno = ENOMEM; + logStrerrorDebug(LOG_TAG, "asprintf failed for new '^.*%s v?[0-9][.][0-9][.][0-9].*$'", termuxPackageManager); + exit(1); + } + + runAllExecWrappersTest("package-manager-version", + 0, 0, + 0, termuxPackageManagerVersionRegex, REG_EXTENDED | REG_ICASE, + termuxPackageManager, termuxPackageManagerPath, environ, + "--version", NULL); + + free(termuxPackageManagerVersionRegex); + + + + free(termuxPackageManagerPath); + +} diff --git a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c new file mode 100644 index 0000000..2b8e9a4 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c @@ -0,0 +1,207 @@ +#include + + + +void test__inspectFileHeader(); +void test__modifyExecEnv(); + + + +void ExecIntercept_runTests() { + logVerbose(LOG_TAG, "ExecIntercept_runTests()"); + + test__inspectFileHeader(); + test__modifyExecEnv(); + + int__AEqual(0, errno); +} + + + + + +void test__inspectFileHeader__Basic(); + +void test__inspectFileHeader() { + logVerbose(LOG_TAG, "test__inspectFileHeader()"); + + test__inspectFileHeader__Basic(); + + int__AEqual(0, errno); +} + +void test__inspectFileHeader__Basic() { + logVVerbose(LOG_TAG, "test__inspectFileHeader__Basic()"); + + char header[TERMUX__FILE_HEADER__BUFFER_SIZE]; + size_t hsize = sizeof(header); + + struct TermuxFileHeaderInfo info = {.interpreterArg = NULL}; + + snprintf(header, hsize, "#!/bin/sh\n") < 0 ? abort() : (void)0; + inspectFileHeader(TERMUX__PREFIX, header, sizeof(header) , &info); + state__ATrue(!info.isElf); + state__ATrue(!info.isNonNativeElf); + string__AEqual(TERMUX__PREFIX__BIN_DIR "/sh", info.interpreterPath); + state__ATrue(info.interpreterArg == NULL); + + snprintf(header, hsize, "#!/bin/sh -x\n") < 0 ? abort() : (void)0; + inspectFileHeader(TERMUX__PREFIX, header, sizeof(header) , &info); + state__ATrue(!info.isElf); + state__ATrue(!info.isNonNativeElf); + string__AEqual(TERMUX__PREFIX__BIN_DIR "/sh", info.interpreterPath); + string__AEqual("-x", info.interpreterArg); + + snprintf(header, hsize, "#! /bin/sh -x\n") < 0 ? abort() : (void)0; + inspectFileHeader(TERMUX__PREFIX, header, sizeof(header) , &info); + state__ATrue(!info.isElf); + state__ATrue(!info.isNonNativeElf); + string__AEqual(TERMUX__PREFIX__BIN_DIR "/sh", info.interpreterPath); + string__AEqual("-x", info.interpreterArg); + + snprintf(header, hsize, "#!/bin/sh -x \n") < 0 ? abort() : (void)0; + inspectFileHeader(TERMUX__PREFIX, header, sizeof(header) , &info); + state__ATrue(!info.isElf); + state__ATrue(!info.isNonNativeElf); + string__AEqual(TERMUX__PREFIX__BIN_DIR "/sh", info.interpreterPath); + string__AEqual("-x", info.interpreterArg); + + snprintf(header, hsize, "#!/bin/sh -x \n") < 0 ? abort() : (void)0; + inspectFileHeader(TERMUX__PREFIX, header, sizeof(header) , &info); + state__ATrue(!info.isElf); + state__ATrue(!info.isNonNativeElf); + string__AEqual(TERMUX__PREFIX__BIN_DIR "/sh", info.interpreterPath); + string__AEqual("-x", info.interpreterArg); + + info.interpreterPath = NULL; + info.interpreterArg = NULL; + // An ELF header for a 32-bit file. + // See https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header + snprintf(header, hsize, "\177ELF") < 0 ? abort() : (void)0; + // Native instruction set. + header[0x12] = EM_NATIVE; + header[0x13] = 0; + inspectFileHeader(TERMUX__PREFIX, header, sizeof(header) , &info); + state__ATrue(info.isElf); + state__ATrue(!info.isNonNativeElf); + state__ATrue(info.interpreterPath == NULL); + state__ATrue(info.interpreterArg == NULL); + + info.interpreterPath = NULL; + info.interpreterArg = NULL; + // An ELF header for a 64-bit file. + // See https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#File_header + snprintf(header, hsize, "\177ELF") < 0 ? abort() : (void)0; + // 'Fujitsu MMA Multimedia Accelerator' instruction set - likely non-native. + header[0x12] = 0x36; + header[0x13] = 0; + inspectFileHeader(TERMUX__PREFIX, header, sizeof(header) , &info); + state__ATrue(info.isElf); + state__ATrue(info.isNonNativeElf); + state__ATrue(info.interpreterPath == NULL); + state__ATrue(info.interpreterArg == NULL); +} + + + + + +void test__modifyExecEnv__unsetLDVars(); +void test__modifyExecEnv__setProcSelfExe(); + +void test__modifyExecEnv() { + logVerbose(LOG_TAG, "test__modifyExecEnv()"); + + test__modifyExecEnv__unsetLDVars(); + test__modifyExecEnv__setProcSelfExe(); + + int__AEqual(0, errno); +} + +void test__modifyExecEnv__unsetLDVars() { + logVVerbose(LOG_TAG, "test__modifyExecEnv__unsetLDVars()"); + + { + char *testEnv[] = {"MY_ENV=1", NULL}; + char **allocatedEnvp; + modifyExecEnv(testEnv, &allocatedEnvp, NULL, true); + state__ATrue(allocatedEnvp != NULL); + string__AEqual(allocatedEnvp[0], "MY_ENV=1"); + state__ATrue(allocatedEnvp[1] == NULL); + free(allocatedEnvp); + } + + { + char *testEnv[] = {"MY_ENV=1", ENV_PREFIX__LD_PRELOAD "a", NULL}; + char **allocatedEnvp; + modifyExecEnv(testEnv, &allocatedEnvp, NULL, true); + state__ATrue(allocatedEnvp != NULL); + string__AEqual(allocatedEnvp[0], "MY_ENV=1"); + state__ATrue(allocatedEnvp[1] == NULL); + free(allocatedEnvp); + } + + { + char *testEnv[] = {"MY_ENV=1", ENV_PREFIX__LD_PRELOAD "a", "A=B", ENV_PREFIX__LD_LIBRARY_PATH "B", "B=C", NULL}; + char **allocatedEnvp; + modifyExecEnv(testEnv, &allocatedEnvp, NULL, true); + state__ATrue(allocatedEnvp != NULL); + string__AEqual(allocatedEnvp[0], "MY_ENV=1"); + string__AEqual(allocatedEnvp[1], "A=B"); + string__AEqual(allocatedEnvp[2], "B=C"); + state__ATrue(allocatedEnvp[3] == NULL); + free(allocatedEnvp); + } +} + +void test__modifyExecEnv__setProcSelfExe() { + logVVerbose(LOG_TAG, "test__modifyExecEnv__setProcSelfExe()"); + + { + char *termuxProcSelfExe = NULL; + state__ATrue(asprintf(&termuxProcSelfExe, "%s%s", ENV_PREFIX__TERMUX_EXEC__PROC_SELF_EXE, TERMUX__PREFIX__BIN_DIR "/bash") != -1); + + char *testEnv[] = {"MY_ENV=1", NULL}; + char **allocatedEnvp; + modifyExecEnv(testEnv, &allocatedEnvp, &termuxProcSelfExe, false); + state__ATrue(allocatedEnvp != NULL); + string__AEqual(allocatedEnvp[0], "MY_ENV=1"); + string__AEqual(allocatedEnvp[1], ENV__TERMUX_EXEC__PROC_SELF_EXE "=" TERMUX__PREFIX__BIN_DIR "/bash"); + free(termuxProcSelfExe); + free(allocatedEnvp); + } + + { + char *termuxProcSelfExe = NULL; + state__ATrue(asprintf(&termuxProcSelfExe, "%s%s", ENV_PREFIX__TERMUX_EXEC__PROC_SELF_EXE, TERMUX__PREFIX__BIN_DIR "/bash") != -1); + + char *testEnv[] = {"MY_ENV=1", ENV__TERMUX_EXEC__PROC_SELF_EXE "=" TERMUX__PREFIX__BIN_DIR "/python", NULL}; + char **allocatedEnvp; + modifyExecEnv(testEnv, &allocatedEnvp, &termuxProcSelfExe, false); + state__ATrue(allocatedEnvp != NULL); + string__AEqual(allocatedEnvp[0], "MY_ENV=1"); + string__AEqual(allocatedEnvp[1], ENV__TERMUX_EXEC__PROC_SELF_EXE "=" TERMUX__PREFIX__BIN_DIR "/bash"); + free(termuxProcSelfExe); + free(allocatedEnvp); + } + + { + char *testEnv[] = {"MY_ENV=1", NULL}; + char **allocatedEnvp; + modifyExecEnv(testEnv, &allocatedEnvp, NULL, false); + state__ATrue(allocatedEnvp != NULL); + string__AEqual(allocatedEnvp[0], "MY_ENV=1"); + state__ATrue(allocatedEnvp[1] == NULL); + free(allocatedEnvp); + } + + { + char *testEnv[] = {"MY_ENV=1", ENV__TERMUX_EXEC__PROC_SELF_EXE "=" TERMUX__PREFIX__BIN_DIR "/python", NULL}; + char **allocatedEnvp; + modifyExecEnv(testEnv, &allocatedEnvp, NULL, false); + state__ATrue(allocatedEnvp != NULL); + string__AEqual(allocatedEnvp[0], "MY_ENV=1"); + state__ATrue(allocatedEnvp[1] == NULL); + free(allocatedEnvp); + } +} diff --git a/run-tests.sh b/run-tests.sh deleted file mode 100755 index 0c831d7..0000000 --- a/run-tests.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/data/data/com.termux/files/usr/bin/bash - -set -u - -for f in tests/*.sh; do - printf "Running $f..." - - EXPECTED_FILE=$f-expected - ACTUAL_FILE=$f-actual - - rm -f $ACTUAL_FILE - $f myarg1 myarg2 &> $ACTUAL_FILE - - if cmp --silent $ACTUAL_FILE $EXPECTED_FILE; then - printf " OK\n" - else - printf " FAILED - compare expected $EXPECTED_FILE with ${ACTUAL_FILE}\n" - fi -done - diff --git a/test-program.c b/test-program.c deleted file mode 100644 index a7f559f..0000000 --- a/test-program.c +++ /dev/null @@ -1,75 +0,0 @@ -#include -#include -#include -#include - -#define TERMUX_APT_PATH "/data/data/com.termux/files/usr/bin/apt" - -extern char **environ; - -int main() { - if (fork() == 0) { - char *const argv[] = { "apt", "--version", NULL }; - printf("# execve\n"); - execve(TERMUX_APT_PATH, argv, environ); - return 0; - } - - sleep(1); - if (fork() == 0) { - printf("# execl\n"); - execl(TERMUX_APT_PATH, "apt", "--version", NULL); - return 0; - } - - sleep(1); - if (fork() == 0) { - printf("# execlp\n"); - execlp("apt", "apt", "--version", NULL); - return 0; - } - - sleep(1); - if (fork() == 0) { - printf("# execle\n"); - execle(TERMUX_APT_PATH, "apt", "--version", NULL, environ); - return 0; - } - - sleep(1); - if (fork() == 0) { - char *const argv[] = { "apt", "--version", NULL }; - printf("# execv\n"); - execv(TERMUX_APT_PATH, argv); - return 0; - } - - sleep(1); - if (fork() == 0) { - char *const argv[] = { "apt", "--version", NULL }; - printf("# execvp\n"); - execvp("apt", argv); - return 0; - } - - sleep(1); - if (fork() == 0) { - char *const argv[] = { "apt", "--version", NULL }; - printf("# execvpe\n"); - execvpe("apt", argv, environ); - return 0; - } - - sleep(1); - if (fork() == 0) { - char *const argv[] = { "apt", "--version", NULL }; - int fd = open(TERMUX_APT_PATH, 0); - printf("# fexecve\n"); - fexecve(fd, argv, environ); - return 0; - } - - sleep(1); - - return 0; -} diff --git a/tests/args-with-spaces.sh b/tests/args-with-spaces.sh deleted file mode 100755 index 1d94c65..0000000 --- a/tests/args-with-spaces.sh +++ /dev/null @@ -1 +0,0 @@ -#!/bin/echo hello world bye diff --git a/tests/args-with-spaces.sh-expected b/tests/args-with-spaces.sh-expected deleted file mode 100644 index 0d06dcc..0000000 --- a/tests/args-with-spaces.sh-expected +++ /dev/null @@ -1 +0,0 @@ -hello world bye tests/args-with-spaces.sh myarg1 myarg2 diff --git a/tests/initial-whitespace.sh b/tests/initial-whitespace.sh deleted file mode 100755 index 5df0a44..0000000 --- a/tests/initial-whitespace.sh +++ /dev/null @@ -1,3 +0,0 @@ -# !/bin/sh - -echo hi2 diff --git a/tests/initial-whitespace.sh-expected b/tests/initial-whitespace.sh-expected deleted file mode 100644 index 7cc3903..0000000 --- a/tests/initial-whitespace.sh-expected +++ /dev/null @@ -1 +0,0 @@ -hi2 diff --git a/tests/not-executable.sh b/tests/not-executable.sh deleted file mode 100644 index 2a22daa..0000000 --- a/tests/not-executable.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -echo hello diff --git a/tests/not-executable.sh-expected b/tests/not-executable.sh-expected deleted file mode 100644 index 5e7557b..0000000 --- a/tests/not-executable.sh-expected +++ /dev/null @@ -1 +0,0 @@ -./run-tests.sh: line 12: tests/not-executable.sh: Permission denied diff --git a/tests/simple.sh b/tests/simple.sh deleted file mode 100755 index 704ea85..0000000 --- a/tests/simple.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -echo hi diff --git a/tests/simple.sh-expected b/tests/simple.sh-expected deleted file mode 100644 index 45b983b..0000000 --- a/tests/simple.sh-expected +++ /dev/null @@ -1 +0,0 @@ -hi diff --git a/tests/usr-bin-env.sh b/tests/usr-bin-env.sh deleted file mode 100755 index ec11f38..0000000 --- a/tests/usr-bin-env.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env sh - -echo hello-user-bin-env-sh diff --git a/tests/usr-bin-env.sh-expected b/tests/usr-bin-env.sh-expected deleted file mode 100644 index 8690c3a..0000000 --- a/tests/usr-bin-env.sh-expected +++ /dev/null @@ -1 +0,0 @@ -hello-user-bin-env-sh From 3d7eecac55565fac00d980f8b1c9ffe052607a50 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 8 Mar 2025 08:57:19 +0500 Subject: [PATCH 19/47] Fixed: Abort `execve()` with a debug error if `argv[0]` length is `>= 128` on Android `< 6` instead of letting process fail post `execve()` without an error For Android `< 6`, the length must not be `>= 128` for the `argv[0]` string of an `execve()` call or library path of a `dlopen()` call. The `soinfo_alloc()` function in `linker.cpp` of Android `/system/bin/linker*` that loaded the `soinfo` of a library/executable had a `SOINFO_NAME_LEN=128` limit on the path/name passed to it before aae859cc, after which it was increased to `PATH_MAX`. Earlier, if path passed was `>= 128`, then `library name "" too long` error would occur. Before dcaef371, the `__linker_init_post_relocation()` function also passed `argv[0]` as executable path to `soinfo_alloc()` function to load its `soinfo`, instead of the actual absolute path of the executable. So before aae859cc, if either length was `>= 128`, then the process would abort with exit code `1`. Note that the `execve()` call itself will not fail, failure occurs before `main()` is called. The limit also applies to the interpreter defined in scripts, as interpreter is passed as `argv[0]` during execution. Both fixes are only available in Android `>= 6`. For earlier versions like Android 5, the path for executables and libraries must be kept below the limit. However, for executables, to allow execution, the `argv[0]` can be shortened even if executable path is longer, or by first changing current working directory to executable's parent directory and then executing it with a relative path. See also `TERMUX__PREFIX_DIR___MAX_LEN`, `TERMUX__PREFIX__BIN_FILE___SAFE_MAX_LEN`, `FILE_HEADER__BINPRM_BUF_SIZE` and `TERMUX__FILE_HEADER__BUFFER_SIZE`. ``` LD_DEBUG=3 /data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary arg1; echo $? 1 linker W [ android linker & debugger ] linker D DEBUG: library name "/data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary" too long ``` ``` (exec -a print-args-binary /data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary arg1); echo $? arg1 0 ``` ``` (cd /data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files && ./print-args-binary arg1); echo $? arg1 0 ``` - https://cs.android.com/android/_/android/platform/bionic/+/dcaef371 - https://cs.android.com/android/_/android/platform/bionic/+/aae859cc - https://github.com/termux/termux-app/issues/213 Credits to @Grimler91 for finding that it was path length that was causing execution failure for binaries. --- Makefile | 6 + .../ld_preload/direct/exec/ExecIntercept.h | 84 ++++++++++++++ .../ld_preload/direct/exec/ExecIntercept.c | 50 +++++++++ .../tests/libtermux-exec_nos_c_tre_tests.in | 14 ++- .../exec/ExecIntercept_RuntimeBinaryTests.c | 105 ++++++++++++------ 5 files changed, 222 insertions(+), 37 deletions(-) diff --git a/Makefile b/Makefile index 680fda0..865e4de 100644 --- a/Makefile +++ b/Makefile @@ -201,6 +201,11 @@ override LIBTERMUX_EXEC__NOS__C__CPPFLAGS := \ override LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR := $(TESTS_BUILD_OUTPUT_DIR)/lib/termux-exec_nos_c_tre +ifneq ($(LIBTERMUX_EXEC__NOS__C__EXECVE_CALL__CHECK_ARGV0_BUFFER_OVERFLOW),1) + override LIBTERMUX_EXEC__NOS__C__EXECVE_CALL__CHECK_ARGV0_BUFFER_OVERFLOW := 0 +endif + + override TERMUX_EXEC__MAIN_APP__TESTS_BUILD_OUTPUT_DIR := $(TESTS_BUILD_OUTPUT_DIR)/app/main @@ -284,6 +289,7 @@ build-libtermux-exec_nos_c_tre: mkdir -p "$$(dirname "$(TMP_BUILD_OUTPUT_DIR)/$$source_file")" || exit $$?; \ $(CC) -c $(CFLAGS) $(LIBTERMUX_EXEC__NOS__C__CPPFLAGS) \ $(TERMUX__CONSTANTS__MACRO_FLAGS) \ + -DLIBTERMUX_EXEC__NOS__C__EXECVE_CALL__CHECK_ARGV0_BUFFER_OVERFLOW=$(LIBTERMUX_EXEC__NOS__C__EXECVE_CALL__CHECK_ARGV0_BUFFER_OVERFLOW) \ -fPIC -fvisibility=default \ -o $(TMP_BUILD_OUTPUT_DIR)/"$$(echo "$$source_file" | sed -E "s/(.*)\.c$$/\1.o/")" \ "$$source_file" || exit $$?; \ diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h index f3335d7..38a989b 100644 --- a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h @@ -55,6 +55,65 @@ extern "C" { * - https://github.com/bminor/bash/blob/bash-5.2/execute_cmd.c#L5964 * - https://github.com/bminor/bash/blob/bash-5.2/execute_cmd.c#L5988 * - https://github.com/bminor/bash/blob/bash-5.2/execute_cmd.c#L6048 + * + * + * + * For Android `< 6`, the length must not be `>= 128` for the `argv[0]` + * string of an `execve()` call or library path of a `dlopen()` call. + * + * The `soinfo_alloc()` function in `linker.cpp` of Android `/system/bin/linker*` + * that loaded the `soinfo` of a library/executable had a `SOINFO_NAME_LEN=128` + * limit on the path/name passed to it before aae859cc, after which it + * was increased to `PATH_MAX`. Earlier, if path passed was `>= 128`, + * then `library name "" too long` error would occur. + * + * Before dcaef371, the `__linker_init_post_relocation()` function also + * passed `argv[0]` as executable path to `soinfo_alloc()` function to + * load its `soinfo`, instead of the actual absolute path of the + * executable. So before aae859cc, if either length was `>= 128`, then + * the process would abort with exit code `1`. Note that the `execve()` + * call itself will not fail, failure occurs before `main()` is called. + * The limit also applies to the interpreter defined in scripts, as + * interpreter is passed as `argv[0]` during execution. + * + * Both fixes are only available in Android `>= 6`. For earlier + * versions like Android 5, the path for executables and libraries + * must be kept below the limit. However, for executables, to allow + * execution, the `argv[0]` can be shortened even if executable path + * is longer, or by first changing current working directory to + * executable's parent directory and then executing it with a relative + * path. + * + * ``` + * LD_DEBUG=3 /data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary arg1; echo $? + * 1 + * + * # Logcat + * linker W [ android linker & debugger ] + * linker D DEBUG: library name "/data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary" too long + * ``` + * + * ``` + * (exec -a print-args-binary /data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary arg1); echo $? + * arg1 + * 0 + * ``` + * + * ``` + * (cd /data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files && ./print-args-binary arg1); echo $? + * arg1 + * 0 + * ``` + * + * - https://cs.android.com/android/_/android/platform/bionic/+/dcaef371 + * - https://cs.android.com/android/_/android/platform/bionic/+/aae859cc + * - https://github.com/termux/termux-app/issues/213 + * + * See also `TERMUX__PREFIX__BIN_FILE___SAFE_MAX_LEN` in + * https://github.com/termux/termux-core-package/blob/master/lib/termux-core_nos_c_tre/include/termux/termux_core__nos__c/v1/termux/file/TermuxFile.h + * + * **See Also:** + * - https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits */ /** @@ -276,6 +335,31 @@ int modifyExecArgs(char *const *argv, const char ***newArgvPointer, +/** + * Check if `argv[0]` length is `>= 128` on Android `< 6` as commands + * will fail with exit code 1 without any error on stderr, + * but with the `library name "" too long` error in + * `logcat` if linker debugging is enabled. + * + * See comment at top of this file. + * + * @param argv The current arguments pointer. + * @param origExecutablePath The originnal executable path passed to + * `execve()`. + * @param executablePath The **normalized** executable or interpreter + * path that will actually be executed. + * @param processedExecutablePath The **normalized** executable path + * that was passed to `execve()`. + * @param interpreterSet Whether a interpreter is set in the executable + * file. + * @return Returns `0` `argv[0]` length is `< 128` or running on + * Android `>= 6`, otherwise `-1` with errno set to `ENAMETOOLONG` if + * buffer overflow would occur.. + */ +int checkExecArg0BufferOverflow(char *const *argv, + const char *executablePath, const char *processedExecutablePath, + bool interpreterSet); + #ifdef __cplusplus } #endif diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c index 493315f..0b695e5 100644 --- a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c +++ b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c @@ -295,6 +295,14 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], + #if defined LIBTERMUX_EXEC__NOS__C__EXECVE_CALL__CHECK_ARGV0_BUFFER_OVERFLOW && LIBTERMUX_EXEC__NOS__C__EXECVE_CALL__CHECK_ARGV0_BUFFER_OVERFLOW == 1 + if (checkExecArg0BufferOverflow(argv, executablePath, processedExecutablePath, interpreterSet) != 0) { + return -1; + } + #endif + + + if (debugLoggingEnabled) { logErrorVerbose(LOG_TAG, "Calling syscall execve"); logErrorVerbose(LOG_TAG, "executable = '%s'", executablePath); @@ -654,3 +662,45 @@ int modifyExecArgs(char *const *argv, const char ***newArgvPointer, return 0; } + + + +int checkExecArg0BufferOverflow(char *const *argv, + const char *executablePath, const char *processedExecutablePath, + bool interpreterSet) { + logErrorVVerbose(LOG_TAG, "Checking argv[0] buffer overflow"); + + size_t argv0Length = strlen(argv[0]); + if (argv0Length >= 128) { + int androidBuildVersionSdk = android_buildVersionSdk_get(); + if (androidBuildVersionSdk < 23) { + bool shouldAbort = false; + char* label = ""; + if (interpreterSet) { + char interpreterHeader[TERMUX__FILE_HEADER__BUFFER_SIZE]; + ssize_t interpreterHeaderLength = readFileHeader("interpreter", executablePath, interpreterHeader, sizeof(interpreterHeader)); + if (interpreterHeaderLength < 0) { + return interpreterHeaderLength; + } + + if (isElfFile(interpreterHeader, interpreterHeaderLength)) { + shouldAbort = true; + label = "interpreted"; + } + } else { + // Is elf. + shouldAbort = true; + label = "executable"; + } + + if (shouldAbort) { + logStrerrorDebug(LOG_TAG, "Cannot execute %s file '%s' as argv[0] '%s' length '%zu' is '>= 128' while running on Android SDK %d", + label, processedExecutablePath, argv[0], argv0Length, androidBuildVersionSdk); + errno = ENAMETOOLONG; + return -1; + } + } + } + + return 0; +} diff --git a/lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in b/lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in index 7dfcfa8..29200cf 100644 --- a/lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in +++ b/lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in @@ -70,11 +70,16 @@ libtermux_exec__nos__c__unit_binary_tests__run_command() { termux_exec__tests__log 1 "Running 'libtermux-exec_nos_c_tre_${unit_binary_tests_variant}'" output="$( + # cd first and execute with relative path to shorten `argv[0]`, + # otherwise command will fail with exit code `1` on Android `< 6` + # without any error if `argv[0]` length is `>= 128`. + # Check `checkExecArg0BufferOverflow()` function in `ExecIntercept.h`. + cd "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/bin" || exit $? printf -v "$TERMUX_EXEC__TESTS__LOG_LEVEL___N" "%s" "$TERMUX_EXEC__TESTS__LOG_LEVEL" || exit $? export "${TERMUX_EXEC__TESTS__LOG_LEVEL___N?}" || exit $? export LD_PRELOAD="$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH" || exit $? ASAN_OPTIONS="fast_unwind_on_malloc=false:detect_leaks=$TERMUX_EXEC__TESTS__DETECT_LEAKS" LSAN_OPTIONS="report_objects=$TERMUX_EXEC__TESTS__DETECT_LEAKS" \ - "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/bin/libtermux-exec_nos_c_tre_${unit_binary_tests_variant}" 2>&1)" + "./libtermux-exec_nos_c_tre_${unit_binary_tests_variant}" 2>&1)" return_value=$? if [ $return_value -eq 0 ] || { [ $return_value -eq 141 ] && @@ -144,10 +149,15 @@ libtermux_exec__nos__c__runtime_binary_tests__run_command() { termux_exec__tests__log 1 "Running 'libtermux-exec_nos_c_tre_${runtime_binary_tests_variant}'" ( + # cd first and execute with relative path to shorten `argv[0]`, + # otherwise command will fail with exit code `1` on Android `< 6` + # without any error if `argv[0]` length is `>= 128`. + # Check `checkExecArg0BufferOverflow()` function in `ExecIntercept.h`. + cd "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/bin" || exit $? printf -v "$TERMUX_EXEC__TESTS__LOG_LEVEL___N" "%s" "$TERMUX_EXEC__TESTS__LOG_LEVEL" || exit $? export "${TERMUX_EXEC__TESTS__LOG_LEVEL___N?}" || exit $? ASAN_OPTIONS="fast_unwind_on_malloc=false:detect_leaks=$TERMUX_EXEC__TESTS__DETECT_LEAKS" LSAN_OPTIONS="report_objects=$TERMUX_EXEC__TESTS__DETECT_LEAKS" \ - "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/bin/libtermux-exec_nos_c_tre_${runtime_binary_tests_variant}" + "./libtermux-exec_nos_c_tre_${runtime_binary_tests_variant}" ) return_value=$? if [ $return_value -ne 0 ]; then diff --git a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c index c19cd63..3cccaa4 100644 --- a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c +++ b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c @@ -1,3 +1,4 @@ +#include #include #include @@ -181,8 +182,8 @@ FEXECVE_CALL_IMPL() } else ((void)0) #define runAllExecWrappersTest(testName, \ - expectedReturnValue, expectedErrno, \ - expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + expectedReturnValue, expectedErrno, expectedReturnValueP, expectedErrnoP, \ + expectedExitCode, expectedOutputRegex, expectedExitCodeP, expectedOutputRegexP, expectedOutputRegexFlags, \ file, path, envp, ...) \ if (1) { \ \ @@ -204,8 +205,8 @@ FEXECVE_CALL_IMPL() } \ { \ /* ExecLP */ \ - runExecTest(testName, expectedReturnValue, expectedErrno, \ - expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + runExecTest(testName, expectedReturnValueP, expectedErrnoP, \ + expectedExitCodeP, expectedOutputRegexP, expectedOutputRegexFlags, \ ExecLP, file, NULL, file, __VA_ARGS__); \ } \ { \ @@ -224,14 +225,14 @@ FEXECVE_CALL_IMPL() } \ { \ /* ExecVP */ \ - runExecTest(testName, expectedReturnValue, expectedErrno, \ - expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + runExecTest(testName, expectedReturnValueP, expectedErrnoP, \ + expectedExitCodeP, expectedOutputRegexP, expectedOutputRegexFlags, \ ExecVP, file, NULL, file, __VA_ARGS__); \ } \ { \ /* ExecVPE */ \ - runExecTest(testName, expectedReturnValue, expectedErrno, \ - expectedExitCode, expectedOutputRegex, expectedOutputRegexFlags, \ + runExecTest(testName, expectedReturnValueP, expectedErrnoP, \ + expectedExitCodeP, expectedOutputRegexP, expectedOutputRegexFlags, \ ExecVPE, file, envp, file, __VA_ARGS__); \ } \ \ @@ -318,8 +319,8 @@ void test__execIntercept__Basic() { logVVerbose(LOG_TAG, "test__execIntercept__Basic()"); runAllExecWrappersTest("rootfs", - -1, EISDIR, - 0, NULL, 0, + -1, EISDIR, -1, EISDIR, + 0, NULL, 0, NULL, 0, "../../", TERMUX__ROOTFS, environ, NULL); } @@ -358,35 +359,69 @@ void test__execIntercept__Files(const char* termuxExec_tests_testsPath, const ch char* testFilePath = NULL; - asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-binary"); - runAllExecWrappersTest("print-args-binary", - 0, 0, - 0, "^goodbye-world$", REG_EXTENDED, - "print-args-binary", testFilePath, environ, - "goodbye-world", NULL); - free(testFilePath); - - asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-binary.sym"); - runAllExecWrappersTest("print-args-binary.sym", - 0, 0, - 0, "^goodbye-world$", REG_EXTENDED, - "print-args-binary.sym", testFilePath, environ, - "goodbye-world", NULL); - free(testFilePath); + int expectedReturnValue = 0; + int expectedErrno = 0; + int expectedReturnValueP = 0; + int expectedErrnoP = 0; + + int expectedExitCode = 0; + char* expectedOutputRegex = "^goodbye-world$"; + int expectedExitCodeP = 0; + char* expectedOutputRegexP = "^goodbye-world$"; + + + // If `argv[0]` length is `>= 128` on Android `< 6`, then commands + // would normally fail with exit code 1 without any error on stderr, + // but `termux-exec` will prevent this by returning `-1` from + // `execveIntercept()` with `ENAMETOOLONG` errno. + // Check `checkExecArg0BufferOverflow()` function in `ExecIntercept.h`. + if (android_buildVersionSdk_get() < 23) { + asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-binary"); + runAllExecWrappersTest("print-args-binary", + -1, ENAMETOOLONG, expectedReturnValueP, expectedErrnoP, + 0, "^$", expectedExitCodeP, expectedOutputRegexP, REG_EXTENDED, + "print-args-binary", testFilePath, environ, + "goodbye-world", NULL); + free(testFilePath); + + asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-binary.sym"); + runAllExecWrappersTest("print-args-binary.sym", + -1, ENAMETOOLONG, expectedReturnValueP, expectedErrnoP, + 0, "^$", expectedExitCodeP, expectedOutputRegexP, REG_EXTENDED, + "print-args-binary.sym", testFilePath, environ, + "goodbye-world", NULL); + free(testFilePath); + } else { + asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-binary"); + runAllExecWrappersTest("print-args-binary", + expectedReturnValue, expectedErrno, expectedReturnValueP, expectedErrnoP, + expectedExitCode, expectedOutputRegex, expectedExitCodeP, expectedOutputRegexP, REG_EXTENDED, + "print-args-binary", testFilePath, environ, + "goodbye-world", NULL); + free(testFilePath); + + asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-binary.sym"); + runAllExecWrappersTest("print-args-binary.sym", + expectedReturnValue, expectedErrno, expectedReturnValueP, expectedErrnoP, + expectedExitCode, expectedOutputRegex, expectedExitCodeP, expectedOutputRegexP, REG_EXTENDED, + "print-args-binary.sym", testFilePath, environ, + "goodbye-world", NULL); + free(testFilePath); + } asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-linux-script.sh"); runAllExecWrappersTest("print-args-linux-script.sh", - 0, 0, - 0, "^goodbye-world$", REG_EXTENDED, + expectedReturnValue, expectedErrno, expectedReturnValueP, expectedErrnoP, + expectedExitCode, expectedOutputRegex, expectedExitCodeP, expectedOutputRegexP, REG_EXTENDED, "print-args-linux-script.sh", testFilePath, environ, "goodbye-world", NULL); free(testFilePath); asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-linux-script.sh.sym"); runAllExecWrappersTest("print-args-linux-script.sh.sym", - 0, 0, - 0, "^goodbye-world$", REG_EXTENDED, + expectedReturnValue, expectedErrno, expectedReturnValueP, expectedErrnoP, + expectedExitCode, expectedOutputRegex, expectedExitCodeP, expectedOutputRegexP, REG_EXTENDED, "print-args-linux-script.sh.sym", testFilePath, environ, "goodbye-world", NULL); free(testFilePath); @@ -394,16 +429,16 @@ void test__execIntercept__Files(const char* termuxExec_tests_testsPath, const ch asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-termux-script.sh"); runAllExecWrappersTest("print-args-termux-script.sh", - 0, 0, - 0, "^goodbye-world$", REG_EXTENDED, + expectedReturnValue, expectedErrno, expectedReturnValueP, expectedErrnoP, + expectedExitCode, expectedOutputRegex, expectedExitCodeP, expectedOutputRegexP, REG_EXTENDED, "print-args-termux-script.sh", testFilePath, environ, "goodbye-world", NULL); free(testFilePath); asprintf_wrapper(&testFilePath, "%s/%s", termuxExec__execTestFilesPath, "print-args-termux-script.sh.sym"); runAllExecWrappersTest("print-args-termux-script.sh.sym", - 0, 0, - 0, "^goodbye-world$", REG_EXTENDED, + expectedReturnValue, expectedErrno, expectedReturnValueP, expectedErrnoP, + expectedExitCode, expectedOutputRegex, expectedExitCodeP, expectedOutputRegexP, REG_EXTENDED, "print-args-termux-script.sh.sym", testFilePath, environ, "goodbye-world", NULL); free(testFilePath); @@ -456,8 +491,8 @@ void test__execIntercept__PackageManager() { } runAllExecWrappersTest("package-manager-version", - 0, 0, - 0, termuxPackageManagerVersionRegex, REG_EXTENDED | REG_ICASE, + 0, 0, 0, 0, + 0, termuxPackageManagerVersionRegex, 0, termuxPackageManagerVersionRegex, REG_EXTENDED | REG_ICASE, termuxPackageManager, termuxPackageManagerPath, environ, "--version", NULL); From 21e6e6344caacda04c315f319fe38902ca52630c Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 27 Oct 2024 22:55:44 +0500 Subject: [PATCH 20/47] Added: Add site, docs and old changelog files under `MIT` license Related commit 2fe47750 --- LICENSE | 6 +- README.md | 90 +--- licenses/termux__termux-exec-package__MIT.md | 23 + .../en/projects/docs/developer/build/index.md | 170 +++++++ .../docs/developer/contribute/index.md | 51 +++ .../pages/en/projects/docs/developer/index.md | 19 + .../en/projects/docs/developer/test/index.md | 83 ++++ site/pages/en/projects/docs/index.md | 26 ++ site/pages/en/projects/docs/install/index.md | 45 ++ .../pages/en/projects/docs/technical/index.md | 151 +++++++ site/pages/en/projects/docs/usage/index.md | 415 ++++++++++++++++++ site/pages/en/projects/index.md | 45 ++ site/pages/en/projects/releases/0/v0.1.md | 19 + site/pages/en/projects/releases/0/v0.2.md | 21 + site/pages/en/projects/releases/0/v0.3.md | 21 + site/pages/en/projects/releases/0/v0.4.md | 21 + site/pages/en/projects/releases/0/v0.5.md | 21 + site/pages/en/projects/releases/0/v0.6.md | 21 + site/pages/en/projects/releases/0/v0.7.md | 21 + site/pages/en/projects/releases/0/v0.8.md | 21 + site/pages/en/projects/releases/0/v0.9.md | 21 + site/pages/en/projects/releases/1/v1.0.md | 21 + site/pages/en/projects/releases/index.md | 55 +++ 23 files changed, 1306 insertions(+), 81 deletions(-) create mode 100644 licenses/termux__termux-exec-package__MIT.md create mode 100644 site/pages/en/projects/docs/developer/build/index.md create mode 100644 site/pages/en/projects/docs/developer/contribute/index.md create mode 100644 site/pages/en/projects/docs/developer/index.md create mode 100644 site/pages/en/projects/docs/developer/test/index.md create mode 100644 site/pages/en/projects/docs/index.md create mode 100644 site/pages/en/projects/docs/install/index.md create mode 100644 site/pages/en/projects/docs/technical/index.md create mode 100644 site/pages/en/projects/docs/usage/index.md create mode 100644 site/pages/en/projects/index.md create mode 100644 site/pages/en/projects/releases/0/v0.1.md create mode 100644 site/pages/en/projects/releases/0/v0.2.md create mode 100644 site/pages/en/projects/releases/0/v0.3.md create mode 100644 site/pages/en/projects/releases/0/v0.4.md create mode 100644 site/pages/en/projects/releases/0/v0.5.md create mode 100644 site/pages/en/projects/releases/0/v0.6.md create mode 100644 site/pages/en/projects/releases/0/v0.7.md create mode 100644 site/pages/en/projects/releases/0/v0.8.md create mode 100644 site/pages/en/projects/releases/0/v0.9.md create mode 100644 site/pages/en/projects/releases/1/v1.0.md create mode 100644 site/pages/en/projects/releases/index.md diff --git a/LICENSE b/LICENSE index aadef44..0f3d0ac 100644 --- a/LICENSE +++ b/LICENSE @@ -7,4 +7,8 @@ Files: Comment: The `termux-exec-package` repository is released under the `Apache-2.0` license, unless specified differently in a file/directory or in any additional `Files` sections below. -License: [Apache-2.0](licenses/termux__termux-exec-package__Apache-2.0.md) +License: [Apache-2.0](licenses/termux__termux-exec-package__Aache-2.0.md) + +Files: + site/* +License: [MIT](licenses/termux__termux-exec-package__MIT.md) diff --git a/README.md b/README.md index aae5c9d..9babae0 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,23 @@ -# termux-exec -A `execve()` wrapper to fix two problems with exec-ing files in Termux. +# termux-exec-package -# Problem 1: Cannot execute files not part of the APK -Android 10 started blocking executing files under the app data directory, as -that is a [W^X](https://en.wikipedia.org/wiki/W%5EX) violation - files should be either -writeable or executable, but not both. Resources: +The [`termux-exec`](https://github.com/termux/termux-exec-package) package provides utils and libraries for Termux exec. It also provides a shared library that is meant to be preloaded with [`$LD_PRELOAD`](https://man7.org/linux/man-pages/man8/ld.so.8.html) for proper functioning of the Termux execution environment. -- [Google Android issue](https://issuetracker.google.com/issues/128554619) -- [Termux: No more exec from data folder on targetAPI >= Android Q](https://github.com/termux/termux-app/issues/1072) -- [Termux: Revisit the Android W^X problem](https://github.com/termux/termux-app/issues/2155) +### Contents -While there is merit in that general principle, this prevents using Termux and Android -as a general computing device, where it should be possible for users to create executable -scripts and binaries. +- [Project](#project) -# Solution 1: Cannot execute files not part of the APK -Create an `exec` interceptor using [LD_PRELOAD](https://en.wikipedia.org/wiki/DLL_injection#Approaches_on_Unix-like_systems), -that instead of executing an ELF file directly, executes `/system/bin/linker64 /path/to/elf`. -Explanation follows below. +--- -On Linux, the kernel is normally responsible for loading both the executable and the -[dynamic linker](https://en.wikipedia.org/wiki/Dynamic_linker). The executable is invoked -by file path with the [execve system call](https://en.wikipedia.org/wiki/Exec_(system_call)). -The kernel loads the executable into the process, and looks for a `PT_INTERP` entry in -its [ELF program header table](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#Program_header) -of the file - this specifies the path to the dynamic linker (`/system/bin/linker64` for 64-bit Android). +  -There is another way to load the two ELF objects: -[since 2018](https://android.googlesource.com/platform/bionic/+/8f639a40966c630c64166d2657da3ee641303194) -the dynamic linker can be invoked directly with `exec`. -If passed the filename of an executable, the dynamic linker will load and run the executable itself. -So, instead of executing `path/to/mybinary`, it's possible to execute -`/system/bin/linker64 /absolute/path/to/mybinary` (the linker needs an absolute path). -This is what `termux-exec` does to circumvent the block on executing files in the data -directory - the kernel sees only `/system/bin/linker64` being executed. -This also means that we need to extract [shebangs](https://en.wikipedia.org/wiki/Shebang_(Unix)). So for example, a call to execute: -```sh -./path/to/myscript.sh -``` -where the script has a `#!/path/to/interpreter` shebang, is replaced with: +## Project -```sh -/system/bin/linker64 /path/to/interpreter ./path/to/myscript.sh -``` +**Check the `termux-exec-package` project info [here](site/pages/en/projects/index.md), including `docs` and `releases` info.** -Implications: +--- -- It's important that `LD_PRELOAD` is kept - see e.g. [this change in sshd](https://github.com/termux/termux-packages/pull/18069). -We could also consider patching this exec interception into the build process of termux packages, so `LD_PRELOAD` would not be necessary for packages built by the termux-packages repository. - -- The executable will be `/system/bin/linker64`. So some programs that inspects the executable name (on itself or other programs) using `/proc/${PID}/exec` or `/proc/${PID}/comm` (where `$(PID}` could be `self`, for the current process) needs to be changed to instead inspect `argv0` of the process instead. See [this llvm driver change](https://github.com/termux/termux-packages/pull/18074) and [this pgrep/pkill change](https://github.com/termux/termux-packages/pull/18075). - -- Statically linked binaries will not work. These are rare in Android and Termux, but zig currently produces statically linked binaries against musl libc. - -- The interception using `LD_PRELOAD` will only work for programs using the [C library wrappers](https://linux.die.net/man/3/execve) for executing a new process, not when using the `execve` system call directly. Luckily most programs do use this. Programs using raw system calls needs to be patched or be run under [proot](https://wiki.termux.com/wiki/PRoot). - -**NOTE**: The above example used `/system/bin/linker64` - on 32-bit systems, the corresponding -path is `/system/bin/linker`. - -**NOTE**: While this circumvents the technical restriction, it still might be considered -violating [Google Play policy](https://support.google.com/googleplay/android-developer/answer/9888379). -So this workaround is not guaranteed to enable Play store distribution of Termux - but it's -worth an attempt, and regardless of Play store distribution, updating the targetSdk is necessary. - -# Problem 2: Shebang paths -A lot of Linux software is written with the assumption that `/bin/sh`, `/usr/bin/env` -and similar file exists. This is not the case on Android where neither `/bin/` nor `/usr/` -exists. - -When building packages for Termux those hard-coded assumptions are patched away - but this -does not help with installing scripts and programs from other sources than Termux packages. - -# Solution 2: Shebang paths -Create an `execve()` wrapper that rewrites calls to execute files under `/bin/` and `/usr/bin` -into the matching Termux executables under `$PREFIX/bin/` and inject that into processes -using `LD_PRELOAD`. - -# How to install -1. Install with `pkg install termux-exec`. -2. Exit your current session and start a new one. -3. From now on shebangs such as `/bin/sh` and `/usr/bin/env python` should work. - -# Where is LD_PRELOAD set? -The `$PREFIX/bin/login` program which is used to create new Termux sessions checks for -`$PREFIX/lib/libtermux-exec.so` and if so sets up `LD_PRELOAD` before launching the login shell. - -Soon, when making a switch to target Android 10+, this will be setup by the Termux app even before -launching any process, as `LD_PRELOAD` will be necessary for anything non-system to execute. +  diff --git a/licenses/termux__termux-exec-package__MIT.md b/licenses/termux__termux-exec-package__MIT.md new file mode 100644 index 0000000..6299e71 --- /dev/null +++ b/licenses/termux__termux-exec-package__MIT.md @@ -0,0 +1,23 @@ +MIT License + +Copyright (c) 2023 termux + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +- https://opensource.org/licenses/MIT diff --git a/site/pages/en/projects/docs/developer/build/index.md b/site/pages/en/projects/docs/developer/build/index.md new file mode 100644 index 0000000..a194461 --- /dev/null +++ b/site/pages/en/projects/docs/developer/build/index.md @@ -0,0 +1,170 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/docs/@ARK_DOC__VERSION@/developer/build/index.md" +--- + +# termux-exec-package Build Docs + + + +The [`termux-exec`](https://github.com/termux/termux-exec-package) package build instructions are available below. For install instructions, check [`install`](../../install/index.md) docs. + +### Contents + +- [Build Methods](#build-methods) + +--- + +  + + + + + +## Build Methods + +The `termux-exec` package provided by Termux is built from the [`termux/termux-exec-package`](https://github.com/termux/termux-exec-package) repository. It can be built with the following methods. + +- [Termux Packages Build Infrastructure](#termux-packages-build-infrastructure) +- [On Device With `make`](#on-device-with-make) + +**The [Termux Packages Build Infrastructure](#termux-packages-build-infrastructure) is the recommended way to build `termux-exec`.** If the `termux-exec` package is built with the [Termux Packages Build Infrastructure](#termux-packages-build-infrastructure), then the Termux variable values in the `Makefile` are dynamically set to the values defined in the [`properties.sh`] file of the build infrastructure by passing them to `make` via the `$TERMUX_PKG_EXTRA_MAKE_ARGS` variable set in the [`packages/termux-exec/build.sh`] file. If `termux-exec` is built with `make` instead, then the hardcoded fallback/default Termux variable values in the `Makefile` will get used during build time, which may affect or break `termux-exec` at runtime if current app/environment is different from the Termux default one (`TERMUX_APP__PACKAGE_NAME=com.termux` and `TERMUX__ROOTFS=/data/data/com.termux/files`). However, if `make` must be used for some reason, and building for a different app/environment than the Termux default, like for a Termux fork or alternate package name/rootfs, then manually update the hardcoded values in the `Makefile` or manually pass the alternate values to the `make` command. + +##   + +  + + + +### Termux Packages Infrastructure + +To build the `termux-exec` package with the [`termux-packages`](https://github.com/termux/termux-packages) build infrastructure, the provided [`build-package.sh`](https://github.com/termux/termux-packages/blob/master/build-package.sh) script can be used. Check the [Build environment](https://github.com/termux/termux-packages/wiki/Build-environment) and [Building packages](https://github.com/termux/termux-packages/wiki/Building-packages) docs for how to build packages. + +#### Default Sources + +To build the `termux-exec` package from its default repository release tag or git branch sources that are used for building the package provided in Termux repositories, just clone the `termux-packages` repository and build. + +```shell +# Clone `termux-packages` repo and switch current working directory to it. +git clone https://github.com/termux/termux-packages.git +cd termux-packages + +# (OPTIONAL) Run termux-packages docker container if running off-device. +./scripts/run-docker.sh + +# Force build package and download dependencies from Termux packages repositories. +./build-package.sh -f -I termux-exec +``` + +#### Local Sources + +To build the `termux-exec` package from its local sources or a pull request branch, clone the `termux-packages` repository, clone/create the `termux-exec-package` repository locally, make required changes to the [`packages/termux-exec/build.sh`] file to update the source url and then build. + +Check [Build Local Package](https://github.com/termux/termux-packages/wiki/Building-packages#build-local-package) and [Package Build Local Source URLs](https://github.com/termux/termux-packages/wiki/Creating-new-package#package-build-local-source-urls) docs for more info on how to building packages from local sources.* + +```shell +# Clone `termux-packages` repo and switch current working directory to it. +git clone https://github.com/termux/termux-packages.git +cd termux-packages + +# Update `$TERMUX_PKG_SRCURL` variable in `packages/termux-exec/build.sh`. +# We use `file:///path/to/source/dir` format for the local source URL. +TERMUX_PKG_SRCURL=file:///home/builder/termux-packages/sources/termux-exec-package +TERMUX_PKG_SHA256=SKIP_CHECKSUM + +# Clone/copy `termux-exec-package` repo at `termux-packages/sources/termux-exec-package` +# directory. Make sure current working directory is root directory of +# termux-packages repo when cloning. +git clone https://github.com/termux/termux-exec-package.git sources/termux-exec-package + +# (OPTIONAL) Manually switch to different (pull) branch that exists on +# origin if required, or to the one defined in $TERMUX_PKG_GIT_BRANCH +# variable of build.sh file, as it will not be automatically checked out. +# By default, the repo default/current branch that's cloned +# will get built, which is usually `master` or `main`. +# Whatever is the current state of the source directory will +# be built as is, including any uncommitted changes to current +# branch. +(cd sources/termux-exec-package; git checkout ) + +# (OPTIONAL) Run termux-packages docker container if running off-device. +./scripts/run-docker.sh + +# Force build package and download dependencies from Termux packages repositories. +./build-package.sh -f -I termux-exec +``` + +##   + +  + + + +### On Device With `make` + +To build `termux-exec` package on the device inside the Termux app with [`make`](https://www.gnu.org/software/make), check below. Do not use a PC to build the package as PC architecture may be different from target device architecture and the `clang` compiler wouldn't have been patched like Termux provided one is so that built packages are compatible with Termux, like patches done for `DT_RUNPATH`. + +```shell +# Install dependencies. +pkg install clang git make termux-create-package + +# For `libtermux-core_nos_c_tre` as build dependency. +pkg install termux-core + +# Clone/copy `termux-exec-package` repo at `termux-exec-package` directory and switch +# current working directory to it. +git clone https://github.com/termux/termux-exec-package.git termux-exec-package +cd termux-exec-package + +# Whatever is the current state of the `termux-exec-package` directory will be built. +# If required, manually switch to different (pull) branch that exists on origin. +git checkout + +# Remove any existing deb files in current directory. +rm -f termux-exec_*.deb + +# Build deb file for the architecture of the host device/clang compiler. +make packaging-debian-build + +# Install. +# We use shell * glob expansion to automatically select the deb file +# regardless of `__.deb` suffix, that's why existing +# deb files were deleted earlier in case any existed with the wrong version. +dpkg -i termux-exec_*.deb +``` + +To build `termux-core` package as well and use its local `libtermux-core_nos_c_tre` build as build dependency for `termux-exec` package, clone its repo and build it with a local install prefix, and pass it to `termux-exec` package `make` build. + +```shell +# Clone/copy `termux-core-package` repo at `termux-core-package` directory +# and switch current working directory to it. +git clone https://github.com/termux/termux-core-package.git termux-core-package +cd termux-core-package + +# Export path to `termux-packages` build repo directory needed to run +# `termux-replace-termux-core-src-scripts` script in `Makefile`. +export TERMUX_PKGS__BUILD__REPO_ROOT_DIR="/path/to/termux-packages/repo/dir" + +# Set `termux-core` package local install prefix path. +export TERMUX_CORE_PKG__INSTALL_PREFIX="$(pwd)/build/install/usr" + +# Build `termux-core` package and install it under local install prefix path. +make +make install + +# Switch current working directory to `termux-exec-package` directory. +cd ../termux-exec-package + +# Build deb file for the architecture of the host device/clang compiler. +make packaging-debian-build +``` + +--- + +  + + + + + +[`packages/termux-exec/build.sh`]: https://github.com/termux/termux-packages/blob/master/packages/termux-exec/build.sh +[`properties.sh`]: https://github.com/termux/termux-packages/blob/master/scripts/properties.sh diff --git a/site/pages/en/projects/docs/developer/contribute/index.md b/site/pages/en/projects/docs/developer/contribute/index.md new file mode 100644 index 0000000..adb21ef --- /dev/null +++ b/site/pages/en/projects/docs/developer/contribute/index.md @@ -0,0 +1,51 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/docs/@ARK_DOC__VERSION@/developer/contribute/index.md" +--- + +# termux-exec-package Contribute Docs + + + +These docs are meant for you if you want to contribute to the [`termux/termux-exec-package`](https://github.com/termux/termux-exec-package) repository. + +### Contents + +- [Commit Messages Guidelines](#commit-messages-guidelines) + +--- + +  + + + + + +## Commit Messages Guidelines + +Commit messages **must** use the [Conventional Commits](https://www.conventionalcommits.org) spec so that changelogs can be automatically generated when [releases](../../../releases/index.md) are made as per the [Keep a Changelog](https://github.com/olivierlacan/keep-a-changelog) spec by the [`create-conventional-changelog`](https://github.com/termux/create-conventional-changelog) script, check its repository for further details on the spec. + +**The first letter for `type` and `description` must be capital and description should be in the present tense.** The space after the colon `:` is necessary. For a breaking change, add an exclamation mark `!` before the colon `:` as an indicator, and it will also cause the change to be automatically highlighted in the changelog. + +``` +[optional scope]: + +[optional body] + +[optional footer(s)] +``` + +**Only the `types` listed below must be used exactly as they are used in the changelog headings.** For example, `Added: Add foo`, `Added|Fixed: Add foo and fix bar`, `Changed!: Change baz as a breaking change`, etc. You can optionally add a scope as well, like `Fixed(docs): Fix some bug`. **Do not use anything else as type, like `add` instead of `Added`, or `chore`, etc.** + +- **Added** for new additions or features. +- **Changed** for changes in existing functionality. +- **Deprecated** for soon-to-be removed features. +- **Fixed** for any bug fixes. +- **Removed** for now removed features. +- **Reverted** for changes that were reverted. +- **Patched** for patches done for specific builds. +- **Security** in case of vulnerabilities. +- **Release** for when a new release is made. + +--- + +  diff --git a/site/pages/en/projects/docs/developer/index.md b/site/pages/en/projects/docs/developer/index.md new file mode 100644 index 0000000..279a829 --- /dev/null +++ b/site/pages/en/projects/docs/developer/index.md @@ -0,0 +1,19 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/docs/@ARK_DOC__VERSION@/developer/index.md" +--- + +# termux-exec-package Developer Docs + + + +These docs are meant for the developers, maintainers and contributors of the [`termux/termux-exec-package`](https://github.com/termux/termux-exec-package) repository and its forks. + +### Contents + +- [Build](build/index.md) +- [Test](test/index.md) +- [Contribute](contribute/index.md) + +--- + +  diff --git a/site/pages/en/projects/docs/developer/test/index.md b/site/pages/en/projects/docs/developer/test/index.md new file mode 100644 index 0000000..0ce7979 --- /dev/null +++ b/site/pages/en/projects/docs/developer/test/index.md @@ -0,0 +1,83 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/docs/@ARK_DOC__VERSION@/developer/test/index.md" +--- + +# termux-exec-package Test Docs + + + +[`termux-exec-tests`](https://github.com/termux/termux-exec-package/blob/master/app/main/tests/termux-exec-tests.in) can be used to run tests for [`termux-exec`](https://github.com/termux/termux-exec-package). + +Install the updated `termux-exec` package and then start a new shell so that changes for updated library get loaded. + +Update `bash termux-tools termux-am` packages to the latest version, otherwise tests will fail. Install Termux:API app and latest `termux-api` package to test them as well. + +To show help, run `"${TERMUX__PREFIX:-$PREFIX}/libexec/installed-tests/termux-exec/app/main/termux-exec-tests" --help`. + +  + +To run all tests, run `"${TERMUX__PREFIX:-$PREFIX}/libexec/installed-tests/termux-exec/app/main/termux-exec-tests" -vv all`. You can optionally run only `unit` or `runtime` tests as well. +- The `unit` tests test different units/components of libraries and executables. +- The `runtime` tests test the runtime execution of libraries and executables. + +  + +Two variants of each test binary is compiled. +- With `fsanitize` enabled with the `-fsanitize=address -fsanitize-recover=address -fno-omit-frame-pointer` flags that contain `-fsanitize` in filename +- Without `fsanitize` enabled that contain `-nofsanitize` in filename. + +This is requires because `fsanitize` does not work on all Android versions/architectures properly and may crash with false positives with the `AddressSantizier: SEGV on unknown address` error, like Android `7` (always crashes) and `x86_64` (requires loop to trigger as occasional crash), even for a source file compiled with an empty `main()` function. + +To enable `AddressSantizier` while running `termux-exec-tests`, pass `-f`. To also enable `LeakSanitizer`, pass `-l` as well, but if it is not supported on current device, the `termux-exec-tests` will error out with `AddressSantizier: detect_leaks is not supported on this platform`. + +If you get `CANNOT LINK EXECUTABLE *: library "libclang_rt.asan-aarch64.so" not found`, like on Android `7`, you will need to install the `libcompiler-rt` package to get the `libclang_rt.asan-aarch64.so` dynamic library required for `AddressSantizier` if passing `-f`. Export library file path with `export LD_LIBRARY_PATH="${TERMUX__PREFIX:-$PREFIX}/lib/clang/17/lib/linux"` before running tests. + +  + +The `libtermux-exec_nos_c_tre_runtime-binary-tests` is also additionally compiled for Android API level `28` by `termux-exec` [`build.sh`](https://github.com/termux/termux-packages/blob/master/packages/termux-exec/build.sh) if `TERMUX_PKG_API_LEVEL` is `< 28` to test APIs that are only available on higher Android versions like `fexecve()`. When `libtermux-exec_nos_c_tre_tests` is executed, the `libtermux_exec__nos__c__runtime_binary_tests__run_command()` function dynamically calls the `libtermux-exec_nos_c_tre_runtime-binary-tests` variant that would be supported by the host device. + +--- + +  + + + + + +## Help + +``` +termux-exec-tests can be used to run tests for 'termux-exec'. + + +Usage: + termux-exec-tests [command_options] + +Available commands: + unit Run unit tests. + runtime Run runtime on-device tests. + all Run all tests. + +Available command_options: + [ -h | --help ] Display this help screen. + [ --version ] Display version. + [ -q | --quiet ] Set log level to 'OFF'. + [ -v | -vv | -vvv | -vvvvv ] + Set log level to 'DEBUG', 'VERBOSE', + 'VVERBOSE' and 'VVVERBOSE'. + [ -f ] Use fsanitize binaries for AddressSanitizer. + [ -l ] Detect memory leaks with LeakSanitizer. + Requires '-f' to be passed. + [ --ld-preload-dir= ] + The directory containing `$LD_PRELOAD' + libraries: 'libtermux-exec*.so'. + [ --no-clean ] Do not clean test files on failure. + [ --test-names-filter= ] + Regex to filter which tests to run by + test name. + [ --tests-path= ] The path to installed-tests directory. +``` + +--- + +  diff --git a/site/pages/en/projects/docs/index.md b/site/pages/en/projects/docs/index.md new file mode 100644 index 0000000..d9b4c71 --- /dev/null +++ b/site/pages/en/projects/docs/index.md @@ -0,0 +1,26 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/docs/@ARK_DOC__VERSION@/index.html" +ark__replacement_strings: + - target: "../releases/index.md" + replacement: "@ARK_PAGE__URL@/../../../releases/index.html" +--- + +# termux-exec-package Docs + + + +The [`termux-exec`](https://github.com/termux/termux-exec-package) package provides utils and libraries for Termux exec. It also provides a shared library that is meant to be preloaded with [`$LD_PRELOAD`](https://man7.org/linux/man-pages/man8/ld.so.8.html) for proper functioning of the Termux execution environment. + +### Contents + +- [Releases](../releases/index.md) +- [Install](install/index.md) +- [Usage](usage/index.md) +- [Technical](technical/index.md) +- [Developer](developer/index.md) + - [Build](developer/build/index.md) + - [Test](developer/test/index.md) + - [Contribute](developer/contribute/index.md) +- [License](https://github.com/termux/termux-exec-package/blob/master/LICENSE) + +--- diff --git a/site/pages/en/projects/docs/install/index.md b/site/pages/en/projects/docs/install/index.md new file mode 100644 index 0000000..667c747 --- /dev/null +++ b/site/pages/en/projects/docs/install/index.md @@ -0,0 +1,45 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/docs/@ARK_DOC__VERSION@/install/index.md" +--- + +# termux-exec-package Install Docs + + + +The [`termux-exec`](https://github.com/termux/termux-exec-package) package install instructions are available below. For build instructions, check [`build`](../developer/build/index.md) docs. + +### Contents + +- [Install Sources](#install-sources) + +--- + +  + + + + + +## Install Sources + +`termux-exec` package can be installed from the following sources. + +- [Termux Packages Repository](#termux-packages-repository) + +##   + +  + + + +### Termux Packages Repository + +To install `termux-exec` package from the [`main` channel](https://github.com/termux/termux-packages/blob/master/packages/termux-exec/build.sh) of the [Termux packages repository](https://packages.termux.dev), run the following commands. + +```shell +pkg install termux-exec +``` + +--- + +  diff --git a/site/pages/en/projects/docs/technical/index.md b/site/pages/en/projects/docs/technical/index.md new file mode 100644 index 0000000..e4fb273 --- /dev/null +++ b/site/pages/en/projects/docs/technical/index.md @@ -0,0 +1,151 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/docs/@ARK_DOC__VERSION@/technical/index.md" +--- + +# termux-exec-package Technical Docs + + + +The [`termux-exec`](https://github.com/termux/termux-exec-package) package provides utils and libraries for Termux exec. It also provides a shared library that is meant to be preloaded with [`$LD_PRELOAD`](https://man7.org/linux/man-pages/man8/ld.so.8.html) for proper functioning of the Termux execution environment. + +There are `2` variants of the `$LD_PRELOAD` library provided by `termux-exec-package`. +1. `libtermux-exec-direct-ld-preload.so` with [`TermuxExecDirectLDPreloadEntryPoint.c`](https://github.com/termux/termux-exec-package/blob/v2.0.0/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c) as entry point that is used for the `direct` execution type. +2. `libtermux-exec-linker-ld-preload.so` with [`TermuxExecLinkerLDPreloadEntryPoint.c.c`](https://github.com/termux/termux-exec-package/blob/v2.0.0/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c) as entry point that is used for the [`system_linker_exec`](#system-linker-exec-solution) execution type. + +The primary `$LD_PRELOAD` library variant to be used is set by copying it to `$TERMUX__PREFIX/usr/lib/libtermux-exec-ld-preload.so` and this path is exported in `$LD_PRELOAD` by [`login`](https://github.com/termux/termux-tools/blob/v1.45.0/scripts/login.in#L42-L54) script. This is done by the [`postinst`](https://github.com/termux/termux-exec-package/blob/v2.0.0/packaging/debian/postinst.in) script run during package installation, which runs [`termux-exec-ld-preload-lib setup`](https://github.com/termux/termux-exec-package/blob/v2.0.0/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in) to set the correct variant as per the execution type required for the Termux environment of the host device by running [`termux-exec-system-linker-exec is-enabled`](https://github.com/termux/termux-exec-package/blob/v2.0.0/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in) to check if `system_linker_exec` is to be enabled. A symlink is not used for `libtermux-exec-ld-preload.so` for potential performance impacts. The `$LD_PRELOAD` variable is currently not exported by the Termux app, like for shell commands started for plugins, check [here](https://github.com/termux/termux-tasker#termux-environment) for more info. + +Both variants intercept `exec()` family of functions and support `system_linker_exec` execution, but the `libtermux-exec-linker-ld-preload.so` is meant to intercept additional functions to solve other issues specific to `system_linker_exec` execution, without affecting performance for users using `direct` execution type. The `libtermux-exec-direct-ld-preload.so` variant needs to support `system_linker_exec` as well as during package updates, before the `linker` variant is set as primary variant, the `direct` variant will get used for certain commands as it gets installed as the primary variant by default, and commands will fail if `system_linker_exec` is required to bypass execution restrictions. + +For backward compatibility, a symlink from `libtermux-exec.so` to `libtermux-exec-ld-preload.so` is also created so that older clients do not break which have exported path to `libtermux-exec.so` in `$LD_PRELOAD` via `login` script of older versions of `termux-tools `package. + +The following functions are intercepted by the `$LD_PRELOAD` library variants, which are internally implemented by [`libtermux-exec_nos_c_tre`](https://github.com/termux/termux-exec-package/tree/v2.0.0/lib/termux-exec_nos_c_tre) c library for the Android native operating system (`nos`) running in Termux runtime environment (`tre`). + +- [`exec()`](#exec) + +Some older devices/ROM do not support setting `$LD_PRELOAD`. ([1](https://github.com/termux/termux-packages/issues/2066), [2](https://github.com/termux/termux-packages/commit/1ec6c042), [3](https://github.com/termux/termux-packages/commit/6fb2bb2f)) + +--- + +  + + + + + +## `exec()` + +The `exec()` family of functions are [declared in `unistd.h`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/include/unistd.h;l=92-100) and [implemented by `exec.cpp`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/bionic/exec.cpp) in android [`bionic`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/README.md) `libc` library. The `exec()` functions are wrappers around the [`execve()`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/SYSCALLS.TXT;l=68) system call listed in [`syscalls(2)`](https://man7.org/linux/man-pages/man2/syscalls.2.html) provided by the [android/linux kernel](https://cs.android.com/android/kernel/superproject/+/ebe69964:common/include/linux/syscalls.h;l=790), which can also be directly called with the [`syscall(2)`](https://man7.org/linux/man-pages/man2/syscall.2.html) library function [declared in `unistd.h`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/include/unistd.h;l=308). Note that there is also a `execve()` wrapper in `unistd.h` around the `execve()` system call. The `termux-exec` overrides the entire `exec()` family of functions, but will not override direct calls to the `execve()` system call via `syscall(2)`, which is usually not directly called by programs. + +The Termux `$LD_PRELOAD` library implements the intercepts in [`ExecIntercept.c`](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/exec/ExecIntercept.c) and [`ExecVariantsIntercept.c`](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/exec/ExecVariantsIntercept.c). + +**See Also:** + +- [exec (system call) wiki](https://en.wikipedia.org/wiki/Exec_(system_call)) +- [`unistd.h` POSIX spec](https://pubs.opengroup.org/onlinepubs/7908799/xsh/unistd.h.html) +- [`execve` POSIX spec](https://pubs.opengroup.org/onlinepubs/7908799/xsh/execve.html) +- [`execve(2)` linux man](https://man7.org/linux/man-pages/man2/execve.2.html) +- [`exec(3)` linux man](https://man7.org/linux/man-pages/man3/exec.3.html) +- [`exec(3p)` linux man](https://man7.org/linux/man-pages/man3/exec.3p.html) + +  + + + +The `termux-exec` overrides the `exec()` family of functions to solve the following issues when exec-ing files in Termux. + +- [App Data File Execute Restrictions](#app-data-file-execute-restrictions) +- [Linux vs Termux `bin` paths](#linux-vs-termux-bin-paths) + +  + + + +### App Data File Execute Restrictions + +Android `>= 10` as part of `W^X` restrictions with the [`0dd738d8`](https://cs.android.com/android/_/android/platform/system/sepolicy/+/0dd738d810532eb41ad8d90520156212ce756648) commit via [SeLinux](https://source.android.com/docs/security/features/selinux) policies removed the `untrusted_app*` domains/process context type assigned to untrusted third party app processes that use [`targetSdkVersion`](https://developer.android.com/guide/topics/manifest/uses-sdk-element#target) `>= 29` to `exec()` their app data files that are assigned the `app_data_file` file context type, like under the `/data/data/` (for user `0`) directory. Two backward compatibility domains were also added for which `exec()` was still allowed, the `untrusted_app_25` domain for apps that use `targetSdkVersion` `<= 25` and `untrusted_app_27` that use `targetSdkVersion` `26-28`. For all `untrusted_app*` domains, `dlopen()` on app data files is still allowed. + +Check [`App Data File Execute Restrictions` android docs](https://github.com/agnostic-apollo/Android-Docs/blob/master/site/pages/en/projects/docs/apps/processes/app-data-file-execute-restrictions.md) for more information on the `W^X` restrictions, including that apply to other app domains. + +While there is merit to prevent execution of untrusted code in writable storage as a general principle, this prevents using Termux and Android as a general purpose computing device, where it should be possible for users to download executable binaries and scripts from trusted locations or compile it locally and then execute it, after user explicitly grants the app the permission to do so with some kind of runtime/development permission provided by Android. + +**See Also:** + +- [`issuetracker#128554619`](https://issuetracker.google.com/issues/128554619) +- [`termux/termux-app#1072`: No more exec from data folder on targetAPI >= Android Q](https://github.com/termux/termux-app/issues/1072) +- [`termux/termux-app#2155`: Revisit the Android W^X problem](https://github.com/termux/termux-app/issues/2155) + +  + +#### System Linker Exec Solution + +Check [`System Linker Exec` android docs](https://github.com/agnostic-apollo/Android-Docs/blob/master/site/pages/en/projects/docs/apps/processes/app-data-file-execute-restrictions.md#system-linker-exec) for detailed info for the solution to bypass `W^X` restrictions. + +The [dynamic linker](https://en.wikipedia.org/wiki/Dynamic_linker) is the part of the operating system that loads and links the shared libraries needed by an executable when it is executed. The kernel is normally responsible for loading both the executable and the dynamic linker. When a [`execve()` system call](https://en.wikipedia.org/wiki/Exec_(system_call)) is made for an [`ELF`](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#Program_header) executable, the kernel loads the executable file, then reads the path to the dynamic linker from the `PT_INTERP` entry in [`ELF` program header table](https://en.wikipedia.org/wiki/Executable_and_Linkable_Format#Program_header) and then attempts to load and execute this other executable binary for the dynamic linker, which then loads the initial executable image and all the dynamically-linked libraries on which it depends and starts the executable. For binaries built for Android, `PT_INTERP` specifies the path to `/system/bin/linker64` for 64-bit binaries and `/system/bin/linker` for 32-bit binaries. You can check `ELF` file headers with the [`readelf`](https://www.man7.org/linux/man-pages/man1/readelf.1.html) command, like `readelf --program-headers --dynamic /path/to/executable`. + +The system provided `linker` at `/system/bin/linker64` on 64-bit Android and `/system/bin/linker` on 32-bit Android can also be passed an absolute path to an executable file on Android `>= 10` for it to execute, even if the executable file itself cannot be executed directly from the app data directory. Note that some 64-devices have 32-bit Android. + +An ELF file at `/data/data/com.foo/executable` can be executed with: + +```shell +/system/bin/linker64 /data/data/com.foo/executable [args] +``` + +A script file at `/data/data/com.foo/script.sh` that has the `#!/path/to/interpreter` [shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) can be executed with: + +```sh +/system/bin/linker64 /path/to/interpreter /data/data/com.foo/script.sh [args] +``` + +This is possible because when a file is executed with the system linker, the kernel/SeLinux only sees the `linker` binary assigned the `system_linker_exec` file context type being executed by the app process and not the `*app_data_file` being executed by the linker. + +Support in Android `linker` to execute files was added in Android `10`, so this method cannot be used on older Android versions, like for some system app domains. For `untrusted_app*` domains, this is not an issue since they can execute files directly on Android `< 10`. + +- https://cs.android.com/android/_/android/platform/bionic/+/8f639a40966c630c64166d2657da3ee641303194 +- https://cs.android.com/android/_/android/platform/bionic/+/refs/tags/android-10.0.0_r1:linker/linker_main.cpp + +The Termux `$LD_PRELOAD` library implements enabling `system_linker_exec` via the `isSystemLinkerExecEnabled()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h#L20), [2](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c#L31)) and `shouldEnableSystemLinkerExecForFile()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h#L55), [2](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c#L2137)) in `TermuxExecLDPreload.h` and implemented by `TermuxExecLDPreload.c`, and called by [`ExecIntercept.c`](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/exec/ExecIntercept.c#L216). + +#### System Linker Exec Issues + +1. The `$LD_PRELOAD` environment variable must be exported and must contain the path to the `termux-exec` library before any app data file is executed, but not for android system binaries under `/system/bin`. It needs to be set for all entry points into termux, including when connecting to a `sshd` server, see [`termux/termux-packages#18069`](https://github.com/termux/termux-packages/pull/18069). We can also consider patching exec interception into the build process of termux packages, so `$LD_PRELOAD` would not be necessary for packages built by the `termux-packages` repository. + +2. The executable file will be `/system/bin/linker64` and not the ELF file meant to be executed, so some programs that inspect the executable name with [`proc`](https://man7.org/linux/man-pages/man5/proc.5.html) files like `/proc//exe` or `/proc//comm` will see the wrong name. For packages that read `/proc/self/exe` file of their own process, the `termux-exec` exports the [`$TERMUX_EXEC__PROC_SELF_EXE`](../usage/index.md#termux_exec__proc_self_exe) environment variable with the absolute path to ELF file being executed so that such packages can be patched to read it first instead, however, this will not work if reading `exe` file of other processes as `TERMUX_EXEC__PROC_SELF_EXE` will only be set for the current process, and not others. Programs like `pgrep`/`pkill` that match process name from its command, will have to be patched to either do full matching (`-f`/`--full`) OR preferably as the linker binary is normally not executed, so during matching to skip the first argument if its for the system linker and second arg is for an app data file under `TERMUX_APP__DATA_DIR` and `TERMUX_EXEC__PROC_SELF_EXE` is set for itself to indicate its running in a `system_linker_exec` environment. See also [`termux/termux-packages#18069`](https://github.com/termux/termux-packages/pull/18075). + +3. Statically linked binaries will not work. These are rare in Android and Termux, but `zig` currently produces statically linked binaries against `musl` `libc`. + +4. Packages that call the `execve()` system call directly will need to be patched to use one of the `exec()` family wrappers or they should be run under [`proot`](https://wiki.termux.com/wiki/PRoot). + +5. This solution is not compliant with Google PlayStore policies as executing code downloaded (or compiled) at runtime from sources outside the Google Play Store, like not packed inside the apk is not allowed. However, if even apps using this like Termux are not approved to be uploaded to PlayStore, it at least allows using currently latest `targetSdkVersion` `= 34` to target Android `14`. + + > An app distributed via Google Play may not modify, replace, or update itself using any method other than Google Play's update mechanism. Likewise, an app may not download executable code (such as dex, JAR, .so files) from a source other than Google Play. This restriction does not apply to code that runs in a virtual machine or an interpreter where either provides indirect access to Android APIs (such as JavaScript in a webview or browser). + > Apps or third-party code, like SDKs, with interpreted languages (JavaScript, Python, Lua, etc.) loaded at run time (for example, not packaged with the app) must not allow potential violations of Google Play policies. + + - https://support.google.com/googleplay/android-developer/answer/9888379?hl=en + +##   + +  + + + +### Linux vs Termux `bin` paths + +A lot of Linux software is written with the assumption that rootfs is at `/` and system binaries under the `/bin` or `/usr/bin` directories and the system binaries for script [shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)) interpreter paths exist at paths like `/bin/sh`, `/usr/bin/env`, etc. + +However, on Android the `/bin` path does not exist on Android `<= 8.1`. On Android `>= 9`, `/bin` is a [symlink to `/system/bin` directory](https://cs.android.com/android/_/android/platform/system/core/+/refs/tags/android-9.0.0_r1:rootdir/init.rc;l=48) added via [`ff1ef9f2`](https://cs.android.com/android/_/android/platform/system/core/+/ff1ef9f2b10d98131ea8945c642dd8388d9b0250). The `/usr` path does not exist on any Android version. + +But the `/system/bin` directory is for the path to android system binaries, not the ones provided by Termux. The rootfs for the linux environment provided by apps like Termux is under their app data directory instead, like under the `/data/data/` (for user `0`) directory or `/data/user//` for other secondary users. For packages compiled for the main Termux app, it is at `/data/data/com.termux/files` and its `bin` path is at `/data/data/com.termux/files/usr/bin`. Check [Termux filesystem layout](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout) for more info. + +When packages are built for Termux with the [`termux-packages`](https://github.com/termux/termux-packages) build infrastructure, the harcoded paths for `/` rootfs in package sources are patched and replaced with the rootfs for Termux, but if `/bin/*` or `/usr/bin/*` paths are executed, like via the shell or if they are set in scripts or programs downloaded from outside the Termux packages repos or written by users themselves, they will either fail to execute with `No such file or directory` errors or will execute the android system binaries under `/system/bin/*` if the same filename exists, instead of executing binaries under the Termux `bin` path. + +  + +#### Solution + +The `termux-exec` if set in `$LD_PRELOAD` environment variable overrides the `exec()` family of functions so that if the executable path is under the `/bin/*` or `/usr/bin/*` directories or if a script that is being executed has the interpreter path set to a path under `/bin/*` or `/usr/bin/*` directories, then the `*/bin/` prefix in the path is replaced with the termux `$TERMUX__PREFIX/bin/` prefix, where `$TERMUX__PREFIX` is the environment variable exported by the Termux app. If `$TERMUX__PREFIX` is not exported or is not a valid absolute path, then the default `TERMUX__PREFIX` set by the `Makefile` during `termux-exec` build time is used instead as the Termux prefix path. + +An alternate solution, especially in case `$LD_PRELOAD` may not be set is to run [`termux-fix-shebang`](https://github.com/termux/termux-tools/blob/master/app/main/scripts/termux-fix-shebang.in) on the script file before executing them. This would have to be done when a script is initially installed/written and whenever its upgraded. + +--- + +  diff --git a/site/pages/en/projects/docs/usage/index.md b/site/pages/en/projects/docs/usage/index.md new file mode 100644 index 0000000..ec9d16b --- /dev/null +++ b/site/pages/en/projects/docs/usage/index.md @@ -0,0 +1,415 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/docs/@DOCS__VERSION@/usage/index.md" +--- + +# termux-exec-package Usage Docs + + + +The [`termux-exec`](https://github.com/termux/termux-exec-package) package provides utils and libraries for Termux exec. It also provides a shared library that is meant to be preloaded with [`$LD_PRELOAD`](https://man7.org/linux/man-pages/man8/ld.so.8.html) by exporting [`LD_PRELOAD="$TERMUX__PREFIX/usr/lib/libtermux-exec-ld-preload.so"`](https://man7.org/linux/man-pages/man8/ld.so.8.html) for proper functioning of the Termux execution environment. + +Note that if exporting `LD_PRELOAD` or updating the `termux-exec` library, the current shell will not automatically load the updated library as any libraries that are to be loaded are only done when process is started. If unsetting `LD_PRELOAD`, the current shell will not automatically unload the library either. Changes to `LD_PRELOAD` requires at least one nested `exec()` for changes to take effect, i.e a new nested shell needs to be started and then any new calls/executions in the nested shell will be intercepted by the library set it `LD_PRELOAD`. + +### Contents + +- [Input Environment Variables](#input-environment-variables) +- [Output Environment Variables](#ouput-environment-variables) +- [Processed Environment Variables](#processed-environment-variables) + +--- + +  + + + + + +## Input Environment Variables + +The `termux-exec` uses the following environment variables as input if required. + +*For variables with type `bool`, the values `1`, `true`, `on`, `yes`, `y` are parsed as `true` and the values `0`, `false`, `off`, `no`, `n` are parsed as `false`, and for any other value the default value will be used.* + +- [`TERMUX_APP__DATA_DIR`](#termux_app__data_dir) +- [`TERMUX_APP__LEGACY_DATA_DIR`](#termux_app__legacy_data_dir) +- [`TERMUX__PREFIX`](#termux__prefix) +- [`TERMUX__SE_PROCESS_CONTEXT`](#termux__se_process_context) +- [`TERMUX_EXEC__LOG_LEVEL`](#termux_exec__log_level) +- [`TERMUX_EXEC__EXECVE_CALL__INTERCEPT`](#termux_exec__execve_call__intercept) +- [`TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE`](#termux_exec__system_linker_exec__mode) +- [`TERMUX_EXEC__TESTS__LOG_LEVEL`](#termux_exec__tests__log_level) + +  + + + +### TERMUX_APP__DATA_DIR + +The non-legacy [Termux app data directory path](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#termux-private-app-data-directory) (`/data/user//` or `/mnt/expand//user/0/`) that is assigned by Android for all Termux app data returned for the [`ApplicationInfo.dataDir`](https://developer.android.com/reference/android/content/pm/ApplicationInfo#dataDir) call, that contains the [Termux project directory](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#termux-project-directory) (`TERMUX__PROJECT_DIR`), and optionally the [Termux rootfs directory](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#termux-rootfs-directory) (`TERMUX__ROOTFS`). The value is automatically exported by the Termux app for app version `>= 0.119.0`. + +**Type:** `string` + +**Commits:** [`8b5f4d9a`](https://github.com/termux/termux-core-package/commit/8b5f4d9a) + +**Version:** [`>= 2.0.0`](https://github.com/termux/termux-exec-package/releases/tag/v2.0.0) + +**Default value:** `/data/user/0/com.termux` if Termux app is running under primary user `0`. + +**Assigned values:** + +- An absolute path with max length `TERMUX_APP__DATA_DIR___MAX_LEN` (`69`) including the null `\0` terminator. + +If `TERMUX_APP__DATA_DIR` environment variable is not set or value is not valid, then the build value set in [`properties.sh`] file for the app data directory with which `termux-exec` package is compiled with is used, which defaults to `/data/data/@TERMUX_APP__PACKAGE_NAME@`. + +The `TERMUX_APP__DATA_DIR___MAX_LEN` is defined in [`TermuxFile.h`](https://github.com/termux/termux-core-package/blob/v0.1.0/lib/termux-core_nos_c_tre/include/termux/termux_core__nos__c/v1/termux/file/TermuxFile.h) and it is the internal buffer length used by `termux-exec` for storing Termux app data directory path and the buffer lengths of all other Termux paths under the app data directory is based on it. The value `69` is the maximum value that will fit the requirement for a valid Android app data directory path, check [termux file path limits](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits) docs for more info. **Packages compiled for Termux must ensure that the `TERMUX_APP__DATA_DIR` value used during compilation is `< TERMUX_APP__DATA_DIR___MAX_LEN`.** + +##   + +  + + + +### TERMUX_APP__LEGACY_DATA_DIR + +The legacy [Termux app data directory path](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#termux-private-app-data-directory) (`/data/data/`) assigned by Android for all Termux app data, that contains the [Termux project directory](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#termux-project-directory) (`TERMUX__PROJECT_DIR`), and optionally the [Termux rootfs directory](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#termux-rootfs-directory) (`TERMUX__ROOTFS`). The value is automatically exported by the Termux app for app version `>= 0.119.0`. + +**Type:** `string` + +**Commits:** [`8b5f4d9a`](https://github.com/termux/termux-core-package/commit/8b5f4d9a) + +**Version:** [`>= 2.0.0`](https://github.com/termux/termux-exec-package/releases/tag/v2.0.0) + +**Default value:** `/data/data/com.termux` if Termux app is running under primary user `0`. + +**Assigned values:** + +- An absolute path with max length `TERMUX_APP__DATA_DIR___MAX_LEN` (`69`) including the null `\0` terminator. + +If `TERMUX_APP__LEGACY_DATA_DIR` environment variable is not set or value is not valid, then the build value set in [`properties.sh`] file for the app data directory with which `termux-exec` package is compiled with is used, which defaults to `/data/data/@TERMUX_APP__PACKAGE_NAME@`. If the build value is a non-legacy path (`/data/user//` or `/mnt/expand//user/0/`), then it is automatically converted to a legacy path. + +The `TERMUX_APP__DATA_DIR___MAX_LEN` is defined in [`TermuxFile.h`](https://github.com/termux/termux-core-package/blob/v0.1.0/lib/termux-core_nos_c_tre/include/termux/termux_core__nos__c/v1/termux/file/TermuxFile.h) and it is the internal buffer length used by `termux-exec` for storing Termux app data directory path and the buffer lengths of all other Termux paths under the app data directory is based on it. The value `69` is the maximum value that will fit the requirement for a valid Android app data directory path, check [termux file path limits](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits) docs for more info. **Packages compiled for Termux must ensure that the `TERMUX_APP__DATA_DIR` value used during compilation is `< TERMUX_APP__DATA_DIR___MAX_LEN`.** + +##   + +  + + + +### TERMUX__PREFIX + +The [Termux prefix directory path](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#termux-prefix-directory) under or equal to the [Termux rootfs directory](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#termux-rootfs-directory) (`TERMUX__ROOTFS`) where all Termux packages data is installed. The value is automatically exported by the Termux app for app version `>= 0.119.0`. + +**Type:** `string` + +**Commits:** [`8b5f4d9a`](https://github.com/termux/termux-core-package/commit/8b5f4d9a) + +**Version:** [`>= 2.0.0`](https://github.com/termux/termux-exec-package/releases/tag/v2.0.0) + +**Default value:** `/data/data/com.termux/files/usr` + +**Assigned values:** + +- An absolute path with max length `TERMUX__PREFIX_DIR___MAX_LEN` (`90`) including the null `\0` terminator. + +If `TERMUX__PREFIX` environment variable is not set or value is not valid, then the build value set in [`properties.sh`] file for the app data directory with which `termux-exec` package is compiled with is used, which defaults to `/data/data/@TERMUX_APP__PACKAGE_NAME@/files/usr`. If the build value is not accessible `termux-exec` intercepts will return with an error and `errno` should be set that is set by the [`access()`](https://man7.org/linux/man-pages/man2/access.2.html) call. + +The `TERMUX__PREFIX_DIR___MAX_LEN` is defined in [`TermuxFile.h`](https://github.com/termux/termux-core-package/blob/v0.1.0/lib/termux-core_nos_c_tre/include/termux/termux_core__nos__c/v1/termux/file/TermuxFile.h) and it is the internal buffer length used by `termux-exec` for storing Termux prefix directory path. The value `90` is the maximum value that will fit the requirement for a valid Termux prefix directory path, check [termux file path limits](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits) docs for more info. **Packages compiled for Termux must ensure that the `TERMUX__PREFIX` value used during compilation is `< TERMUX__PREFIX_DIR___MAX_LEN`.** + +##   + +  + + + +### TERMUX__SE_PROCESS_CONTEXT + +The SeLinux process context of the Termux app process and its child processes. The value is automatically exported by the Termux app for app version `>= 0.119.0`. + +This is used while deciding whether to use `system_linker_exec` if [`TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE`](#termux_exec__system_linker_exec__mode) is set to `enabled`. + +**Type:** `string` + +**Commits:** [`8b5f4d9a`](https://github.com/termux/termux-core-package/commit/8b5f4d9a) + +**Version:** [`>= 2.0.0`](https://github.com/termux/termux-exec-package/releases/tag/v2.0.0) + +**Default value:** `u:r:untrusted_app_27:s0:cXXX,cXXX,c512,c768` if Termux app is running under primary user `0` and using [`targetSdkVersion`](https://developer.android.com/guide/topics/manifest/uses-sdk-element#target) `= 28` where `XXX` would be for the app uid for the [categories](https://github.com/agnostic-apollo/Android-Docs/blob/master/site/pages/en/projects/docs/os/selinux/security-context.md#categories) component. + +**Assigned values:** + +- A valid Android SeLinux process context that matches `REGEX__PROCESS_CONTEXT`: +`^u:r:[^\n\t\r :]+:s0(:c[0-9]+,c[0-9]+(,c[0-9]+,c[0-9]+)?)?$` + +If `TERMUX__SE_PROCESS_CONTEXT` is not set or value is not valid, then the process context is read from the `/proc/self/attr/current` file. + +The `REGEX__PROCESS_CONTEXT` is defined in [`SelinuxUtils.h`](https://github.com/termux/termux-core-package/blob/v0.1.0/lib/termux-core_nos_c_tre/include/termux/termux_core__nos__c/v1/unix/os/selinux/SelinuxUtils.h), check its field docs for more info on the format of the an Android SeLinux process context. + +**See Also:** + +- [Security Context](https://github.com/agnostic-apollo/Android-Docs/blob/master/site/pages/en/projects/docs/os/selinux/security-context.md) +- [`untrusted_app` process context type](https://github.com/agnostic-apollo/Android-Docs/blob/master/site/pages/en/projects/docs/os/selinux/context-types.md#untrusted_app) + +##   + +  + + + +### TERMUX_EXEC__LOG_LEVEL + +The log level for `termux-exec`. + +Normally, `termux-exec` does not log anything at log level `1` (`NORMAL`) for intercepts even and will require setting log level to `>= 2` (`DEBUG`) to see log messages. + +**Type:** `int` + +**Commits:** [`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073) + +**Version:** [`>= 2.0.0`](https://github.com/termux/termux-exec-package/releases/tag/v2.0.0) + +**Default value:** `1` + +**Supported values:** + +- `0` (`OFF`) - Log nothing. + +- `1` (`NORMAL`) - Log error, warn and info messages and stacktraces. + +- `2` (`DEBUG`) - Log debug messages. + +- `3` (`VERBOSE`) - Log verbose messages. + +- `4` (`VVERBOSE`) - Log very verbose messages. + +- `5` (`VVVERBOSE`) - Log very very verbose messages. + +##   + +  + + + +### TERMUX_EXEC__EXECVE_CALL__INTERCEPT + +Whether `termux-exec` should intercept `execve()` wrapper calls [declared in `unistd.h`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/include/unistd.h;l=95) and [implemented by `exec.cpp`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/bionic/exec.cpp;l=187) in android [`bionic`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/README.md) `libc` library around the [`execve()`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/SYSCALLS.TXT;l=68) system call listed in [`syscalls(2)`](https://man7.org/linux/man-pages/man2/syscalls.2.html) provided by the [linux kernel](https://cs.android.com/android/kernel/superproject/+/ebe69964:common/include/linux/syscalls.h;l=790). + +If enabled, then Termux specific logic will run to solve the issues for exec-ing files in Termux that are listed in [`exec()` technical docs](../technical.md#exec) before calling `execve()` system call. If not enabled, then `execve()` system call will be called directly instead. + +The other wrapper functions in the `exec()` family of functions declared in `unistd.h` are always intercepted to solve some other issues on older Android versions, check [`libc/bionic/exec.cpp`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/bionic/exec.cpp) git history. + +**Type:** `string` + +**Commits:** [`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073) + +**Version:** [`>= 2.0.0`](https://github.com/termux/termux-exec-package/releases/tag/v2.0.0) + +**Default value:** `enable` + +**Supported values:** + +- `disable` - Intercept `execve()` will be disabled. + +- `enable` - Intercept `execve()` will be enabled. + +##   + +  + + + +### TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE + +Whether to use [System Linker Exec Solution](../technical.md#system-linker-exec-solution), like to bypass [App Data File Execute Restrictions](../technical.md#app-data-file-execute-restrictions). + +**Type:** `string` + +**Commits:** [`db738a11`](https://github.com/termux/termux-exec-package/commit/db738a11) + +**Version:** [`>= 2.0.0`](https://github.com/termux/termux-exec-package/releases/tag/v2.0.0) + +**Default value:** `enable` + +**Supported values:** + +- `disable` - The `system_linker_exec` will be disabled. + +- `enable` - The `system_linker_exec` will be enabled but only if required. + +- `force` - The `system_linker_exec` will be force enabled even if not required and is supported. + +This is implemented by `isSystemLinkerExecEnabled()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h#L20), [2](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c#L31)) and `shouldEnableSystemLinkerExecForFile()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h#L55), [2](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c#L2137)) in `TermuxExecLDPreload.h` and implemented by `TermuxExecLDPreload.c`, and called by [`ExecIntercept.c`](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/exec/ExecIntercept.c#L216). + +If `disable` is set, then `system_linker_exec` will never be used and the default `direct` execution type will be used. + +If `enable` is set, then `system_linker_exec` will only be used if: +- `system_linker_exec` is required to bypass [App Data File Execute Restrictions](../technical.md#app-data-file-execute-restrictions), i.e device is running on Android `>= 10`. +- Effective user does not equal root (`0`) and shell (`2000`) user (used for [`adb`](https://developer.android.com/tools/adb)). +- [`TERMUX__SE_PROCESS_CONTEXT`](#TERMUX__SE_PROCESS_CONTEXT) does not start with `PROCESS_CONTEXT_PREFIX__UNTRUSTED_APP_25` (`u:r:untrusted_app_25:`) and `PROCESS_CONTEXT_PREFIX__UNTRUSTED_APP_27` (`u:r:untrusted_app_27:`) for which restrictions are exempted. For more info on them, check [`SelinuxUtils.h`](https://github.com/termux/termux-core-package/blob/v0.1.0/lib/termux-core_nos_c_tre/include/termux/termux_core__nos__c/v1/unix/os/selinux/SelinuxUtils.h). +- Executable or interpreter path is under [`TERMUX_APP__DATA_DIR`] or [`TERMUX_APP__LEGACY_DATA_DIR`] directory. + +If `force` is set, then `system_linker_exec` will only be used if: +- `system_linker_exec` is supported, i.e device is running on Android `>= 10`. +- Executable or interpreter path is under [`TERMUX_APP__DATA_DIR`] or [`TERMUX_APP__LEGACY_DATA_DIR`] directory. +This can be used if running in an untrusted app with `targetSdkVersion` `<= 28`. + +The executable or interpreter paths are checked under [`TERMUX_APP__DATA_DIR`]/[`TERMUX_APP__LEGACY_DATA_DIR`] instead of `TERMUX__ROOTFS` as files could be executed from `TERMUX__APPS_DIR` and `TERMUX__CACHE_DIR`, which are not under the Termux rootfs. Additionally, Termux rootfs may not exist under app data directory at all and could be under another directory under Android rootfs `/`, like if compiling packages for `shell` user for the `com.android.shell` package with the Termux rootfs under `/data/local/tmp` instead of `/data/data/com.android.shell` (and using `force` mode) or compiling packages for `/system` directory. + +To get whether `termux-exec` will use `system_linker_exec` at runtime, run the `termux-exec-system-linker-exec is-enabled` command. + +##   + +  + + + +### TERMUX_EXEC__TESTS__LOG_LEVEL + +The log level for `termux-exec-tests`. + +**Type:** `int` + +**Commits:** [`ad67e020`](https://github.com/termux/termux-exec-package/commit/ad67e020) + +**Version:** [`>= 2.0.0`](https://github.com/termux/termux-exec-package/releases/tag/v2.0.0) + +**Default value:** `1` + +**Supported values:** + +- `0` (`OFF`) - Log nothing. + +- `1` (`NORMAL`) - Log error, warn and info messages and stacktraces. + +- `2` (`DEBUG`) - Log debug messages. + +- `3` (`VERBOSE`) - Log verbose messages. + +- `4` (`VVERBOSE`) - Log very verbose messages. + +- `5` (`VVVERBOSE`) - Log very very verbose messages. + +--- + +  + + + + + +## Output Environment Variables + +The `termux-exec` sets the following environment variables if required. + +- [`TERMUX_EXEC__PROC_SELF_EXE`](#termux_exec__proc_self_exe) + +  + + + +### TERMUX_EXEC__PROC_SELF_EXE + +If `system_linker_exec` is being used, then to execute the `/path/to/executable` file, we will be executing `/system/bin/linker64 /path/to/executable [args]`. + + The `/proc/pid/exe` file is a symbolic link containing the actual path of the executable being executed. However, if using `system_linker_exec`, then `/proc/pid/exe` will contain `/system/bin/linker64` instead of `/path/to/executable`. + +So `termux-exec` sets the `TERMUX_EXEC__PROC_SELF_EXE` env variable when `execve` is intercepted to the processed (normalized, absolutized and prefixed) path for the executable file that is to be executed with the linker. This allows patching software that reads `/proc/self/exe` in `termux-packages` build infrastructure to instead use `getenv("TERMUX_EXEC__PROC_SELF_EXE")`. + +  + +Note that if `termux-exec` is set in `LD_PRELOAD`, and it sets `TERMUX_EXEC__PROC_SELF_EXE` for the current process/shell, and then `LD_PRELOAD` is unset, then new processes after second nested `exec()` will get old and wrong value of `TERMUX_EXEC__PROC_SELF_EXE` belonging to the first nested process since `termux-exec` will not get called for the second nested process to set the updated value. The `termux-exec` will be called for the first nested process, because just unsetting `LD_PRELOAD` in current process will not unload the `termux-exec` library and it requires at least one nested `exec()`. The `termux-exec` library could unset `TERMUX_EXEC__PROC_SELF_EXE` if `LD_PRELOAD` isn't already set, but then if the first nested process is under [`TERMUX_APP__DATA_DIR`]/[`TERMUX_APP__LEGACY_DATA_DIR`], it will not have access to `TERMUX_EXEC__PROC_SELF_EXE` to read the actual value of the execution command. This would normally not be an issue if `LD_PRELOAD` being set to the `termux-exec` library is mandatory so that it can `system_linker_exec` commands if running with `targetSdkVersion` `>= 29` on an android `>= 10` device, as otherwise permission denied errors would trigger for any command under [`TERMUX_APP__DATA_DIR`]/[`TERMUX_APP__LEGACY_DATA_DIR`] anyways, unless user manually wraps second nested process with `/system/bin/linker64`. This will still be an issue if `system_linker_exec` is optional due to running with an older `targetSdkVersion` or on an older android device and `TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE` is set to `force`, since then `TERMUX_EXEC__PROC_SELF_EXE` would get exported and will be used by termux packages. + +**To prevent issues, if `LD_PRELOAD` is unset in current process, then `TERMUX_EXEC__PROC_SELF_EXE` must also be unset in the first nested process by the user themselves.** For example, running following will echo `/bin/sh` value twice instead of `/bin/sh` first and `/bin/dash` second if `LD_PRELOAD` were to be set. + +- `system_linker_exec` optional: `LD_PRELOAD= $TERMUX__PREFIX/bin/sh -c 'echo $TERMUX_EXEC__PROC_SELF_EXE'; $TERMUX__PREFIX/bin/dash -c "echo \$TERMUX_EXEC__PROC_SELF_EXE"'` +- `system_linker_exec` mandatory: `LD_PRELOAD= $TERMUX__PREFIX/bin/sh -c 'echo $TERMUX_EXEC__PROC_SELF_EXE'; /system/bin/linker64 $TERMUX__PREFIX/bin/dash -c "echo \$TERMUX_EXEC__PROC_SELF_EXE"'` + +**See Also:** + +- https://man7.org/linux/man-pages/man5/procfs.5.html + +**Type:** `string` + +**Commits:** [`db738a11`](https://github.com/termux/termux-exec-package/commit/db738a11) + +**Version:** [`>= 2.0.0`](https://github.com/termux/termux-exec-package/releases/tag/v2.0.0) + +**Assigned values:** + +- The normalized, absolutized and prefixed path to the executable file is being executed by `execve()` if `system_linker_exec` is being used. + +--- + +  + + + + + +## Processed Environment Variables + +The `termux-exec` processes the following environment variables if required. + +- [`LD_LIBRARY_PATH`](#ld_library_path) +- [`LD_PRELOAD`](#ld_preload) + +  + + + +### LD_LIBRARY_PATH + +The list of directory paths separated with colons `:` that should be searched in for dynamic shared libraries to link programs against. + +**See Also:** + +- https://manpages.debian.org/testing/manpages/ld.so.8.en.html#LD_LIBRARY_PATH + +**Type:** `string` + +**Commits:** [`13831552`](https://github.com/termux/termux-exec-package/commit/13831552), [`8b5f4d9a`](https://github.com/termux/termux-core-package/commit/8b5f4d9a) + +**Version:** [`>= 0.9`](https://github.com/termux/termux-exec-package/releases/tag/v0.9) + +**Processing:** + +- If `execve` is intercepted, then `LD_LIBRARY_PATH` will be unset if executing a non native ELF file (like 32-bit binary on a 64-bit host) and executable path starts with `/system/`, but does not equal `/system/bin/sh`, `system/bin/linker` or `/system/bin/linker64`. + +##   + +  + + + +### LD_PRELOAD + +The list of ELF shared object paths separated with colons ":" to be loaded before all others. This feature can be used to selectively override functions in other shared objects. + +**See Also:** + +- https://manpages.debian.org/testing/manpages/ld.so.8.en.html#LD_PRELOAD + +**Type:** `string` + +**Commits:** [`13831552`](https://github.com/termux/termux-exec-package/commit/13831552), [`8b5f4d9a`](https://github.com/termux/termux-core-package/commit/8b5f4d9a) + +**Version:** [`>= 0.9`](https://github.com/termux/termux-exec-package/releases/tag/v0.9) + +**Processing:** + +- If `execve` is intercepted, then `LD_PRELOAD` will be unset if executing a non native ELF file (like 32-bit binary on a 64-bit host) and executable path starts with `/system/`, but does not equal `/system/bin/sh`, `system/bin/linker` or `/system/bin/linker64`. + +##   + +  + +--- + +  + + + + + +[`properties.sh`]: https://github.com/termux/termux-packages/blob/master/scripts/properties.sh +[`TERMUX_APP__DATA_DIR`]: #termux_app__data_dir +[`TERMUX_APP__LEGACY_DATA_DIR`]: #termux_app__legacy_data_dir diff --git a/site/pages/en/projects/index.md b/site/pages/en/projects/index.md new file mode 100644 index 0000000..bc9ce30 --- /dev/null +++ b/site/pages/en/projects/index.md @@ -0,0 +1,45 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/index.html" +ark__replacement_strings: +- target: "docs/index.md" + replacement: "@ARK_PAGE__URL@/../docs/latest/index.html" +- target: "docs/developer/contribute/index.md" + replacement: "@ARK_PAGE__URL@/../docs/latest/developer/contribute/index.html" +--- + +# termux-exec-package + +The [`termux-exec`](https://github.com/termux/termux-exec-package) package provides utils and libraries for Termux exec. It also provides a shared library that is meant to be preloaded with [`$LD_PRELOAD`](https://man7.org/linux/man-pages/man8/ld.so.8.html) for proper functioning of the Termux execution environment. + +### Contents + +- [Releases](#releases) +- [Docs](#docs) + +--- + +  + + + + + +## Releases + +Check `releases` [here](releases/index.md). + +--- + +  + + + + + +## Docs + +Check `docs` [here](docs/index.md). If you intend to contribute to this repository, then also check `contribute` docs [here](docs/developer/contribute/index.md). + +--- + +  diff --git a/site/pages/en/projects/releases/0/v0.1.md b/site/pages/en/projects/releases/0/v0.1.md new file mode 100644 index 0000000..8703af3 --- /dev/null +++ b/site/pages/en/projects/releases/0/v0.1.md @@ -0,0 +1,19 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/0/v0.1.html" +--- + +# termux-exec-package v0.1 - 2017-09-17 + +## Changelog + +**Commit history:** [`v0.1`](https://github.com/termux/termux-exec-package/releases/tag/v0.1) + +  + + + +Initial release. + +--- + +  diff --git a/site/pages/en/projects/releases/0/v0.2.md b/site/pages/en/projects/releases/0/v0.2.md new file mode 100644 index 0000000..11e2022 --- /dev/null +++ b/site/pages/en/projects/releases/0/v0.2.md @@ -0,0 +1,21 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/0/v0.2.html" +--- + +# termux-exec-package v0.2 - 2017-09-17 + +## Changelog + +**Commit history:** [`v0.1...v0.2`](https://github.com/termux/termux-exec-package/compare/v0.1...v0.2) + +  + + + +### Fixed + +- Fixes argument being lost. ([`48f9e3ab`](https://github.com/termux/termux-exec-package/commit/48f9e3ab)) + +--- + +  diff --git a/site/pages/en/projects/releases/0/v0.3.md b/site/pages/en/projects/releases/0/v0.3.md new file mode 100644 index 0000000..6f87553 --- /dev/null +++ b/site/pages/en/projects/releases/0/v0.3.md @@ -0,0 +1,21 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/0/v0.3.html" +--- + +# termux-exec-package v0.3 - 2017-10-01 + +## Changelog + +**Commit history:** [`v0.2...v0.3`](https://github.com/termux/termux-exec-package/compare/v0.2...v0.3) + +  + + + +### Fixed + +- Check the executable permission of files. Closes [#5](https://github.com/termux/termux-exec-package/issues/5). ([`e127449a`](https://github.com/termux/termux-exec-package/commit/e127449a)) + +--- + +  diff --git a/site/pages/en/projects/releases/0/v0.4.md b/site/pages/en/projects/releases/0/v0.4.md new file mode 100644 index 0000000..3377de2 --- /dev/null +++ b/site/pages/en/projects/releases/0/v0.4.md @@ -0,0 +1,21 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/0/v0.4.html" +--- + +# termux-exec-package v0.4 - 2019-05-10 + +## Changelog + +**Commit history:** [`v0.3...v0.4`](https://github.com/termux/termux-exec-package/compare/v0.3...v0.4) + +  + + + +### Fixed + +- Respect `LDFLAGS` in `Makefile`. ([`d0ab1427`](https://github.com/termux/termux-exec-package/commit/d0ab1427)) + +--- + +  diff --git a/site/pages/en/projects/releases/0/v0.5.md b/site/pages/en/projects/releases/0/v0.5.md new file mode 100644 index 0000000..ebeb349 --- /dev/null +++ b/site/pages/en/projects/releases/0/v0.5.md @@ -0,0 +1,21 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/0/v0.5.html" +--- + +# termux-exec-package v0.5 - 2020-02-21 + +## Changelog + +**Commit history:** [`v0.4...v0.5`](https://github.com/termux/termux-exec-package/compare/v0.4...v0.5) + +  + + + +### Added + +- Start android 10 proot wrapping experiment. ([`1720bb25`](https://github.com/termux/termux-exec-package/commit/1720bb25)) + +--- + +  diff --git a/site/pages/en/projects/releases/0/v0.6.md b/site/pages/en/projects/releases/0/v0.6.md new file mode 100644 index 0000000..d4f5167 --- /dev/null +++ b/site/pages/en/projects/releases/0/v0.6.md @@ -0,0 +1,21 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/0/v0.6.html" +--- + +# termux-exec-package v0.6 - 2020-10-31 + +## Changelog + +**Commit history:** [`v0.5...v0.6`](https://github.com/termux/termux-exec-package/compare/v0.5...v0.6) + +  + + + +### Fixed + +- Don't hardcode `/data/data/com.termux/*`. ([`7736a840`](https://github.com/termux/termux-exec-package/commit/7736a840)) + +--- + +  diff --git a/site/pages/en/projects/releases/0/v0.7.md b/site/pages/en/projects/releases/0/v0.7.md new file mode 100644 index 0000000..1ed798a --- /dev/null +++ b/site/pages/en/projects/releases/0/v0.7.md @@ -0,0 +1,21 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/0/v0.7.html" +--- + +# termux-exec-package v0.7 - 2020-10-31 + +## Changelog + +**Commit history:** [`v0.6...v0.7`](https://github.com/termux/termux-exec-package/compare/v0.6...v0.7) + +  + + + +### Changed + +- Specify `DTERMUX_BASE_DIR` and `TERMUX_PREFIX` from `Makefile`. ([`4ca2778`](https://github.com/termux/termux-exec-package/commit/4ca2778)) + +--- + +  diff --git a/site/pages/en/projects/releases/0/v0.8.md b/site/pages/en/projects/releases/0/v0.8.md new file mode 100644 index 0000000..b76a4a1 --- /dev/null +++ b/site/pages/en/projects/releases/0/v0.8.md @@ -0,0 +1,21 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/0/v0.8.html" +--- + +# termux-exec-package v0.8 - 2020-10-31 + +## Changelog + +**Commit history:** [`v0.7...v0.8`](https://github.com/termux/termux-exec-package/compare/v0.7...v0.8) + +  + + + +### Fixed + +- Use the correct buffer offsets. ([`6b3499ea`](https://github.com/termux/termux-exec-package/commit/6b3499ea)) + +--- + +  diff --git a/site/pages/en/projects/releases/0/v0.9.md b/site/pages/en/projects/releases/0/v0.9.md new file mode 100644 index 0000000..4636f7c --- /dev/null +++ b/site/pages/en/projects/releases/0/v0.9.md @@ -0,0 +1,21 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/0/v0.9.html" +--- + +# termux-exec-package v0.9 - 2021-02-09 + +## Changelog + +**Commit history:** [`v0.5...v0.9`](https://github.com/termux/termux-exec-package/compare/v0.5...v0.9) + +  + + + +### Fixed + +- Automatically unset `LD_PRELOAD` for `/system/bin` programs and when architecture does not match. Implemented by @easyaspi314 in [#17](https://github.com/termux/termux-exec-package/pull/17). ([`13831552`](https://github.com/termux/termux-exec-package/commit/13831552), [`2e71dbd8`](https://github.com/termux/termux-exec-package/commit/2e71dbd8)) + +--- + +  diff --git a/site/pages/en/projects/releases/1/v1.0.md b/site/pages/en/projects/releases/1/v1.0.md new file mode 100644 index 0000000..0550330 --- /dev/null +++ b/site/pages/en/projects/releases/1/v1.0.md @@ -0,0 +1,21 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/1/v1.0.html" +--- + +# termux-exec-package v1.0 - 2021-05-15 + +## Changelog + +**Commit history:** [`v0.9...v1.0`](https://github.com/termux/termux-exec-package/compare/v0.9...v1.0) + +  + + + +### Fixed + +- Update `Makefile` to respect `DESTDIR`. ([`b33392b5`](https://github.com/termux/termux-exec-package/commit/b33392b5)) + +--- + +  diff --git a/site/pages/en/projects/releases/index.md b/site/pages/en/projects/releases/index.md new file mode 100644 index 0000000..ea20690 --- /dev/null +++ b/site/pages/en/projects/releases/index.md @@ -0,0 +1,55 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/index.html" +--- + +# termux-exec-package Releases + +This page lists the releases info for [`termux-exec-package`](https://github.com/termux/termux-exec-package). + +**The currently latest release of `termux-exec` is [`v1.0`](1/v1.0.md).** + +--- + +  + + + + + +## Release Sources + +The `termux-exec` is released on the following sources. + +- [GitHub releases](https://github.com/termux/termux-exec-package/releases). + +--- + +  + + + + + +## Release Versions + +Open a release to view its release notes and/or changelogs. Changelogs are generated as per the [Keep a Changelog](https://github.com/olivierlacan/keep-a-changelog) spec. + +### `v0` + +- [`v0.1`](0/v0.1.md) +- [`v0.2`](0/v0.2.md) +- [`v0.3`](0/v0.3.md) +- [`v0.4`](0/v0.4.md) +- [`v0.5`](0/v0.5.md) +- [`v0.6`](0/v0.6.md) +- [`v0.7`](0/v0.7.md) +- [`v0.8`](0/v0.8.md) +- [`v0.9`](0/v0.9.md) + +### `v1` + +- [`v1.0`](1/v1.0.md) + +--- + +  From f211fb95869a8d9673535b80177c8713a326cb0b Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 27 Feb 2025 00:19:58 +0500 Subject: [PATCH 21/47] Changed: Update `.gitignore` to new format --- .gitignore | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index 794d717..a972f4d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,3 @@ -*.so -*.o -*-actual -*.swo -*.swp +/build/install/* +/build/output/* *.deb -test-binary From 289c15e1064b45d3773613dacd0b6638d586b02b Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Fri, 28 Feb 2025 18:34:18 +0500 Subject: [PATCH 22/47] Release: 2.0.0 --- Makefile | 2 +- site/pages/en/projects/releases/2/v2.0.0.md | 106 ++++++++++++++++++++ site/pages/en/projects/releases/index.md | 4 + 3 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 site/pages/en/projects/releases/2/v2.0.0.md diff --git a/Makefile b/Makefile index 865e4de..7314ae1 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export TERMUX_EXEC_PKG__VERSION := 1.0 +export TERMUX_EXEC_PKG__VERSION := 2.0.0 export TERMUX_EXEC_PKG__ARCH export TERMUX_EXEC_PKG__INSTALL_PREFIX export TERMUX_EXEC_PKG__TESTS__API_LEVEL := diff --git a/site/pages/en/projects/releases/2/v2.0.0.md b/site/pages/en/projects/releases/2/v2.0.0.md new file mode 100644 index 0000000..7283d12 --- /dev/null +++ b/site/pages/en/projects/releases/2/v2.0.0.md @@ -0,0 +1,106 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/2/v2.0.0.html" +--- + +# termux-exec-package v2.0.0 - 2025-03-21 + +## Changelog + +**Commit history:** [`v1.0...v2.0.0`](https://github.com/termux/termux-exec-package/compare/v1.0...v2.0.0) + +  + + + +### Added + +- The `libtermux-exec_nos_c_tre` c library has been added to handle all the `LD_PRELOAD` intercepts implementation, and other functionality required for `termux-exec` like the environment/config. This can be used statically or dynamically for `termux-exec` executables/libraries and also for other packages. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) + +- The `libtermux-exec-direct-ld-preload.so` `$LD_PRELOAD` library variant has been added. It statically depends on `libtermux-exec_nos_c_tre` library and primarily includes `app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c`, which now exclusively only defines the functions intercepted by `libtermux-exec-direct-ld-preload.so` and directs them to their intercepts in respective source files of `libtermux-exec_nos_c_tre` library. The `libtermux-exec-direct-ld-preload.so` is used as primary `$LD_PRELOAD` library variant by copying it to `$TERMUX__PREFIX/usr/lib/libtermux-exec-ld-preload.so` and this path is exported in `$LD_PRELOAD` by the `login` script. For backward compatibility, `libtermux-exec.so` is also created as a copy of `libtermux-exec-ld-preload.so` so that older clients do not break which have exported path to `libtermux-exec.so` in `$LD_PRELOAD`. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) + +- The intercepts implementation of `execve` done by `src/termux-exec.c` has been moved to `lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c`. The `src/exec-variants.c` has been moved to `lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.c` which handles intercepts of the entire `exec()` family of functions except `execve()`. The intercept implementations are now called from `app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c`. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) + +  + +- Intercept the entire `exec()` family of functions, which is required for Android `14`. Closes termux/termux-packages#18537, termux/termux-app#3758. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- Added the `string` environment variable `TERMUX_EXEC__EXECVE_CALL__INTERCEPT` for whether `execve` would be intercepted for shebang fix or `system_linker_exec`. If set to `enable`, then `execve()` intercept will be enabled. If set to `disable`, then `execve()` intercept will be disabled. The default value is `enable`. The other wrapper functions in the `exec()` family of functions declared in `unistd.h` are always intercepted to solve some other issues on older Android versions, check [`libc/bionic/exec.cpp`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/bionic/exec.cpp) git history. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) + +  + +- Add support to execute ELF files by passing them to `/system/bin/linker*` to bypass android app data file exec restriction if using `targetSdkVersion` `>= 28` on Android `>= 10`. Check [technical](https://github.com/termux/termux-exec-package/blob/master/site/pages/en/projects/docs/technical/index.md) and [system linker exec](https://github.com/agnostic-apollo/Android-Docs/blob/master/site/pages/en/projects/docs/apps/processes/app-data-file-execute-restrictions.md#system-linker-exec) docs for info on how `system_linker_exec` works and why it is needed. ([`db738a11`](https://github.com/termux/termux-exec-package/commit/db738a11)) +- The `libtermux-exec-linker-ld-preload.so` `$LD_PRELOAD` library variant has been added and is meant to intercept additional functions to solve other issues specific to `system_linker_exec` execution. The `libtermux-exec-direct-ld-preload.so` variant needs to support `system_linker_exec` as well as during package updates, before the `linker` variant is set as primary variant, the `direct` variant will get used for certain commands as it gets installed as the primary variant by default, and commands will fail if `system_linker_exec` is required to bypass execution restrictions. The primary `$LD_PRELOAD` library variant to be used is set by copying it to `$TERMUX__PREFIX/usr/lib/libtermux-exec-ld-preload.so` and this path is exported in `$LD_PRELOAD` by `login` script. This is done by the `postinst` script run during package installation, which runs `termux-exec-ld-preload-lib setup` to set the correct variant as per the execution type required for the Termux environment of the host device by running `termux-exec-system-linker-exec is-enabled` to check if `system_linker_exec` is to be enabled. ([`db738a11`](https://github.com/termux/termux-exec-package/commit/db738a11)) +- Since when executing with linker, the `/proc/self/exe` will be set to linker path, export `TERMUX_EXEC__PROC_SELF_EXE` environment variable with actual path to executable being executed so that packages can be patched to read it instead. Additional hocking will need to be done for programs that read `/proc//exe`. ([`db738a11`](https://github.com/termux/termux-exec-package/commit/db738a11)) +- Added the `string` `TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE` environment variable for whether to use `system_linker_exec` if `TERMUX_EXEC__EXECVE_CALL__INTERCEPT` is enabled. If set to `disable`, `system_linker_exec` will be disabled. If set to `enable`, then `system_linker_exec` will be enabled but only if required. If set to `force`, then `system_linker_exec` will be force enabled even if not required and is supported. The default value is `enable`. Check `isSystemLinkerExecEnabled()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h#L20), [2](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c#L31)) and `shouldEnableSystemLinkerExecForFile()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h#L55), [2](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c#L2137)) in `TermuxExecLDPreload.h` and implemented by `TermuxExecLDPreload.c` for more info and how its handled. The `system_linker_exec` will now engage for executable or interpreter paths that are under `TERMUX_APP__DATA_DIR` or `TERMUX_APP__LEGACY_DATA_DIR` instead of `TERMUX__ROOTFS` (`TERMUX_BASE_DIR`). ([`db738a11`](https://github.com/termux/termux-exec-package/commit/db738a11)) + +  + +- Added logger framework with multiple log levels with log entries for all the important variable states to track logic. The singular `TERMUX_EXEC_DEBUG` environment variable has been removed. The `int` `TERMUX_EXEC__LOG_LEVEL` environment variable controls the log level based on `(OFF=0, NORMAL=1, DEBUG=2, VERBOSE=3, VVERBOSE=4 and VVVERBOSE=5)`. The default value is `1`. Normally, `termux-exec` does not log anything at default log level `1` (`NORMAL`) for intercepts even and will require setting log level to `>= 2` (`DEBUG`) to see log messages. To enable `VVERBOSE` logging for a command, you can run something like `TERMUX_EXEC__LOG_LEVEL=4 id -u`. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- Added `TERMUX_EXEC_PKG__VERSION` `Makefile` parameter that gets logged on intercept for `termux-exec` package version currently installed. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) + +  + +- Added testing framework via `app/main/tests/termux-exec-tests.in` and `lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in` that calls `termux-exec/lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c` to run unit tests, and `lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c` and `lib/termux-exec_nos_c_tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in` for runtime tests. Old tests files in random places have been removed. The entire `exec()` family of functions is also tested by `lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c`. Docs will be added in a later commit.Tests can be run with `"${TERMUX__PREFIX:-$PREFIX}/libexec/installed-tests/termux-exec/app/main/termux-exec-tests -vv all"`. ([`ad67e020`](https://github.com/termux/termux-exec-package/commit/ad67e020)) + +  + +- Add site, docs and old changelog files under `MIT` license. ([`2fe47750`](https://github.com/termux/termux-exec-package/commit/2fe47750), [`21e6e634`](https://github.com/termux/termux-exec-package/commit/21e6e634)) + +##   + +  + + + +### Changed + +- Use `TERMUX__PREFIX` environment variable to generate Termux bin path to replace `/bin` and `/usr/bin` path in shebang of scripts instead of using hardcoded `TERMUX_BASE_DIR` build variable. If environment variable is not set or is invalid as per `TERMUX__ROOTFS_DIR___MAX_LEN`, then we use the default Termux prefix for which package was compiled for as long as its executable and readable to ensure termux-exec was not compiled for a different package. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- Increase buffer size for executable file shebang header from `256` to `340` defined by `TERMUX__FILE_HEADER__BUFFER_SIZE` as per termux path limits, check comment in `ExecIntercept.h` file and [Termux File Path Limits](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits) docs. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- Decrease max length of valid `TERMUX__ROOTFS` from `200` to `86` defined by `TERMUX__ROOTFS_DIR___MAX_LEN`, check `TermuxFile.h` file and [Termux File Path Limits](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits) docs. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- Move `termux-exec` Apache 2.0 license file content from `LICENSE` to `licenses/termux__termux-exec__Apache-2.0.md` file and use `debian/copyright` format in `LICENSE` file. ([`7ccbc61e`](https://github.com/termux/termux-exec-package/commit/7ccbc61e)) + +##   + +  + + + +### Fixed + +- Fix relative paths for interpreter path by absolutizing it. Previously, only prefix was being replaced. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- Fix `argv[0]` for executing shell scripts where it should be set to the original interpreter set in the file as is instead of the `argv[0]` to `execve()` being intercepted for the executable. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- Fix `fexecve()` where executable path would be `/proc/self/fd/` and checking if its under Termux app data directory directory would give wrong results. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- Fix checking if executable is under Termux directories. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- Fix hardcoded `com.termux` values being used, all constants are replaced during building including the root scope of environment variables that are read and as per `TERMUX_ENV__S_ROOT` defined in `properties.sh` of `termux-pacakges` as `TERMUX_`. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- Abort `execve()` with a debug error if `argv[0]` length is `>= 128` on Android `< 6` instead of letting process fail post `execve()` without an error. ([`e726fba2`](https://github.com/termux/termux-exec-package/commit/e726fba2)) +- Fix issues where `errno` may already be set when `execve` is entered, check comment in `init()` function of `TermuxExecProcess.c` where it is set to `0`. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- The `termux-exec-package.json` will now have correct version and be consistent with `build.sh`, and also include tests files, which wasn't being done before. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- The `termux-exec-package.json` previously had hardcoded `aarch64` as architecture, now we find and replace it for the compiler based on which predefined architecture macro is defined. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) + +##   + +  + + + +### Removed + +- Removed the singular `TERMUX_EXEC_OPTOUT` environment variable. Opt outs should be confined to specific intercepts and logics. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- The singular `TERMUX_EXEC_DEBUG` environment variable has been removed. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) + +--- + +  + + + + + +## Notes + +- Usage docs are available [here](https://github.com/termux/termux-exec-package/blob/master/site/pages/en/projects/docs/usage/index.md). You can run commands with `TERMUX_EXEC__LOG_LEVEL=4 id -u` after installing package for debugging. To run on normal termux with `targetSdkVersion` `<= 28`, run `TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE=force TERMUX_EXEC__LOG_LEVEL=4 id -u` instead. **Check [`TERMUX_EXEC__LOG_LEVEL`](https://github.com/termux/termux-exec-package/blob/master/site/pages/en/projects/docs/usage/index.md#termux_exec__log_level), [`TERMUX_EXEC__EXECVE_CALL__INTERCEPT`](https://github.com/termux/termux-exec-package/blob/master/site/pages/en/projects/docs/usage/index.md#termux_exec__execve_call__intercept), [`TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE`](https://github.com/termux/termux-exec-package/blob/master/site/pages/en/projects/docs/usage/index.md#termux_exec__system_linker_exec__mode) variable docs for more info on how to control behaviour of `termux-exec`.** +- Testing docs are available [here](https://github.com/termux/termux-exec-package/blob/master/site/pages/en/projects/docs/developer/test/index.md). Run tests with `TERMUX_ROOTFS__PACKAGE_MANAGER=apt "${TERMUX__PREFIX:-$PREFIX}/libexec/installed-tests/termux-exec/app/main/termux-exec-tests" -vv all`. Exporting `TERMUX_ROOTFS__PACKAGE_MANAGER` variables is necessary for running tests on older Termux app versions without scoped environment variable changes for package manager tests to run. +- Build docs are available [here](https://github.com/termux/termux-exec-package/blob/master/site/pages/en/projects/docs/developer/build/index.md). + +--- + +  diff --git a/site/pages/en/projects/releases/index.md b/site/pages/en/projects/releases/index.md index ea20690..923a67e 100644 --- a/site/pages/en/projects/releases/index.md +++ b/site/pages/en/projects/releases/index.md @@ -50,6 +50,10 @@ Open a release to view its release notes and/or changelogs. Changelogs are gener - [`v1.0`](1/v1.0.md) +### `v2` + +- [`v2.0.0`](2/v2.0.0.md) + ---   From ac9272253a4cdac05640c700b9168d25b6fb4d17 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 22 Mar 2025 14:27:06 +0500 Subject: [PATCH 23/47] Fixed(postinst): Skip setting primary Termux `$LD_PRELOAD` library if `$ANDROID__BUILD_VERSION_SDK` is not exported and `getprop` command at `/system/bin/getprop` is not accessible either --- packaging/debian/postinst.in | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/packaging/debian/postinst.in b/packaging/debian/postinst.in index 049fcea..86b6024 100644 --- a/packaging/debian/postinst.in +++ b/packaging/debian/postinst.in @@ -12,7 +12,36 @@ main() { log "Start" - termux-exec-ld-preload-lib setup -v || return $? + + local is_android_build_version_sdk_available="true" + local android_build_version_sdk="${ANDROID__BUILD_VERSION_SDK:-}" + case "$android_build_version_sdk" in + ''|*[!0-9]*) + android_build_version_sdk="$(getprop "ro.build.version.sdk")" || true + case "$android_build_version_sdk" in + ''|*[!0-9]*) + log_error "Failed to get android_build_version_sdk value from 'getprop': '$android_build_version_sdk'" + is_android_build_version_sdk_available="false" + ;; + esac + ;; + esac + if [ "$is_android_build_version_sdk_available" = "true" ]; then + log "android_build_version_sdk: '$android_build_version_sdk'" + fi + + + # `termux-exec-system-linker-exec` called by `termux-exec-ld-preload-lib` + # will fail if `ANDROID__BUILD_VERSION_SDK` is not exported + # (by Termux app) and `getprop` command at `/system/bin/getprop` + # is not accessible. + # - https://github.com/termux/termux-app/issues/4448 + if [ "$is_android_build_version_sdk_available" = "true" ]; then + termux-exec-ld-preload-lib setup -v || return $? + else + log_error "Skipping setting primary Termux '\$LD_PRELOAD' library as android_build_version_sdk value is not available" + fi + log "End" From 2f6d9f92e828bfa2152e9182784e71e4644a50c0 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 22 Mar 2025 14:29:19 +0500 Subject: [PATCH 24/47] Fixed(system-linker-exec): Assume app data file execute restrictions are exempted if '/proc/self/attr/current' is not accessible as SeLinux may not be supported on the device --- .../termux-exec-system-linker-exec.in | 35 ++++++++++++------- .../ld_preload/TermuxExecLDPreload.c | 5 +++ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in index e1f0c52..4d3ba91 100644 --- a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in +++ b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in @@ -128,22 +128,31 @@ termux_exec__system_linker_exec__enabled__run_command() { else # If running on Android `>= 10`. if [ "$android_build_version_sdk" -ge 29 ]; then - local se_process_context + local app_data_file_exec_exempted="false" + if [ -f "/proc/self/attr/current" ] && [ -r "/proc/self/attr/current" ]; then + local se_process_context - return_value=0 - se_process_context="$(cat "/proc/self/attr/current")" || return_value=$? - if [ "$return_value" -ne 0 ] || [ -z "$se_process_context" ]; then - termux_exec__system_linker_exec__log_error "Failed to get se_process_context value from '/proc/self/attr/current': '$se_process_context'" - return 1 + return_value=0 + se_process_context="$(cat "/proc/self/attr/current")" || return_value=$? + if [ "$return_value" -ne 0 ] || [ -z "$se_process_context" ]; then + termux_exec__system_linker_exec__log_error "Failed to get se_process_context value from '/proc/self/attr/current': '$se_process_context'" + return 1 + fi + + case "$se_process_context" in + "u:r:untrusted_app_25:"*|"u:r:untrusted_app_27:"*) + app_data_file_exec_exempted="true" + ;; + esac + termux_exec__system_linker_exec__log_error_for_level 2 "se_process_context_from_file: '$se_process_context'" + else + # If '/proc/self/attr/current' is not accessible, + # then SeLinux may not be supported on the device. + termux_exec__system_linker_exec__log_error_for_level 2 "Failed to find or read '/proc/self/attr/current' to read se_process_context value" + termux_exec__system_linker_exec__log_error_for_level 2 "se_process_context_available: 'false'" + app_data_file_exec_exempted="true" fi - termux_exec__system_linker_exec__log_error_for_level 2 "se_process_context_from_file: '$se_process_context'" - local app_data_file_exec_exempted="false" - case "$se_process_context" in - "u:r:untrusted_app_25:"*|"u:r:untrusted_app_27:"*) - app_data_file_exec_exempted="true" - ;; - esac termux_exec__system_linker_exec__log_error_for_level 2 "app_data_file_exec_exempted: '$app_data_file_exec_exempted'" if [ "$app_data_file_exec_exempted" = "false" ]; then diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c index 676c71f..c817e3c 100644 --- a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c +++ b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c @@ -111,6 +111,11 @@ int isSystemLinkerExecEnabled() { if (getSeProcessContextSuccess) { appDataFileExecExempted = stringStartsWith(seProcessContext, PROCESS_CONTEXT_PREFIX__UNTRUSTED_APP_25) || stringStartsWith(seProcessContext, PROCESS_CONTEXT_PREFIX__UNTRUSTED_APP_27); + } else { + // If even '/proc/self/attr/current' is not accessible, + // then SeLinux may not be supported on the device. + logErrorVVerbose(LOG_TAG, "se_process_context_available: '0'"); + appDataFileExecExempted = true; } } From c5f65a3be82865b579322b8a59cda0c4fb7f5576 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 22 Mar 2025 14:44:31 +0500 Subject: [PATCH 25/47] Added: Add `riscv64` support - https://github.com/termux/termux-packages/commits/refs/heads/dev/riscv64/ Co-authored-by: @truboxl Co-authored-by: @agnostic-apollo Closes #28 --- Makefile | 2 ++ .../api/termux_exec/ld_preload/direct/exec/ExecIntercept.h | 2 ++ 2 files changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 7314ae1..cd9c9d4 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,8 @@ ifeq ($(TERMUX_EXEC_PKG__ARCH),) override TERMUX_EXEC_PKG__ARCH := aarch64 else ifneq (,$(findstring $(SPACE)#define __arm__ 1$(SPACE),$(SPACE)$(PREDEFINED_MACROS)$(SPACE))) override TERMUX_EXEC_PKG__ARCH := arm + else ifneq (,$(findstring $(SPACE)#define __riscv 1$(SPACE),$(SPACE)$(PREDEFINED_MACROS)$(SPACE))) + override TERMUX_EXEC_PKG__ARCH := riscv64 else $(error Unsupported package arch) endif diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h index 38a989b..a37ebcb 100644 --- a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h @@ -199,6 +199,8 @@ struct TermuxFileHeaderInfo { #define EM_NATIVE EM_X86_64 #elif defined(__i386__) #define EM_NATIVE EM_386 +#elif defined(__riscv) +# define EM_NATIVE EM_RISCV #else #error "unknown arch" #endif From 1adbb1d1b0357c637c7d432c0a76e459bbfe8307 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 22 Mar 2025 15:23:16 +0500 Subject: [PATCH 26/47] Release: 2.1.0 --- Makefile | 2 +- site/pages/en/projects/releases/2/v2.1.0.md | 32 +++++++++++++++++++++ site/pages/en/projects/releases/index.md | 3 +- 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 site/pages/en/projects/releases/2/v2.1.0.md diff --git a/Makefile b/Makefile index cd9c9d4..17b0bd3 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export TERMUX_EXEC_PKG__VERSION := 2.0.0 +export TERMUX_EXEC_PKG__VERSION := 2.1.0 export TERMUX_EXEC_PKG__ARCH export TERMUX_EXEC_PKG__INSTALL_PREFIX export TERMUX_EXEC_PKG__TESTS__API_LEVEL := diff --git a/site/pages/en/projects/releases/2/v2.1.0.md b/site/pages/en/projects/releases/2/v2.1.0.md new file mode 100644 index 0000000..ae2e498 --- /dev/null +++ b/site/pages/en/projects/releases/2/v2.1.0.md @@ -0,0 +1,32 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/2/v2.1.0.html" +--- + +# termux-exec-package v2.1.0 - 2025-03-22 + +## Changelog + +**Commit history:** [`v2.0.0...v2.1.0`](https://github.com/termux/termux-exec-package/compare/v2.0.0...v2.1.0) + +  + + + +### Added + +- Add `riscv64` support. ([`c5f65a3b`](https://github.com/termux/termux-exec-package/commit/c5f65a3b)) + +##   + +  + + + +### Fixed + +- system-linker-exec: Assume app data file execute restrictions are exempted if `/proc/self/attr/current` is not accessible as SeLinux may not be supported on the device. ([`2f6d9f92`](https://github.com/termux/termux-exec-package/commit/2f6d9f92)) +- postinst: Skip setting primary Termux `$LD_PRELOAD` library if `$ANDROID__BUILD_VERSION_SDK` is not exported and `getprop` command at `/system/bin/getprop` is not accessible either. ([`ac927225`](https://github.com/termux/termux-exec-package/commit/ac927225)) + +--- + +  diff --git a/site/pages/en/projects/releases/index.md b/site/pages/en/projects/releases/index.md index 923a67e..fb54e50 100644 --- a/site/pages/en/projects/releases/index.md +++ b/site/pages/en/projects/releases/index.md @@ -6,7 +6,7 @@ page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/index.html This page lists the releases info for [`termux-exec-package`](https://github.com/termux/termux-exec-package). -**The currently latest release of `termux-exec` is [`v1.0`](1/v1.0.md).** +**The currently latest release of `termux-exec` is [`v2.1.0`](2/v2.1.0.md).** --- @@ -53,6 +53,7 @@ Open a release to view its release notes and/or changelogs. Changelogs are gener ### `v2` - [`v2.0.0`](2/v2.0.0.md) +- [`v2.1.0`](2/v2.1.0.md) --- From b566e7ff4c71ef03a66905eac26f4f2147cf0417 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 23 Mar 2025 22:28:37 +0500 Subject: [PATCH 27/47] Fixed(system-linker-exec): Reattempt fix done in 2f6d9f92 to assume app data file execute restrictions are exempted if `/proc/self/attr/current` is not accessible as SeLinux may not be supported on the device Testing if `/proc/self/attr/current` would be readable with `test -f` and `test -r` is not enough, as `cat` may later fail with `Illegal argument`, so just rely on `cat` itself succeeding before comparing context. Closes #30 --- .../termux-exec-system-linker-exec.in | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in index 4d3ba91..7bbd421 100644 --- a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in +++ b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in @@ -80,8 +80,6 @@ termux_exec__system_linker_exec__main() { ## termux_exec__system_linker_exec__enabled__run_command() { - local return_value - local output_variable_name="${1:-}" termux_exec__system_linker_exec__set_variable "$output_variable_name" "" || return $? @@ -129,28 +127,26 @@ termux_exec__system_linker_exec__enabled__run_command() { # If running on Android `>= 10`. if [ "$android_build_version_sdk" -ge 29 ]; then local app_data_file_exec_exempted="false" - if [ -f "/proc/self/attr/current" ] && [ -r "/proc/self/attr/current" ]; then - local se_process_context - - return_value=0 - se_process_context="$(cat "/proc/self/attr/current")" || return_value=$? - if [ "$return_value" -ne 0 ] || [ -z "$se_process_context" ]; then - termux_exec__system_linker_exec__log_error "Failed to get se_process_context value from '/proc/self/attr/current': '$se_process_context'" - return 1 - fi - + # If '/proc/self/attr/current' is not readable, + # then SeLinux may not be supported on the device. + # Reading the file with `cat` may fail with `Illegal argument` + # error on such devices, even though `test -f` and `test -r` + # commands for the `access()` call succeed. + # `cat: /proc/self/attr/current: Illegal argument` + # - https://github.com/termux/termux-exec-package/issues/30 + local se_process_context + se_process_context="$(cat "/proc/self/attr/current")" || true + if [ -z "$se_process_context" ]; then + termux_exec__system_linker_exec__log_error "Failed to read se_process_context value from '/proc/self/attr/current': '$se_process_context'" + termux_exec__system_linker_exec__log_error_for_level 2 "se_process_context_available: 'false'" + app_data_file_exec_exempted="true" + else + termux_exec__system_linker_exec__log_error_for_level 2 "se_process_context_from_file: '$se_process_context'" case "$se_process_context" in "u:r:untrusted_app_25:"*|"u:r:untrusted_app_27:"*) app_data_file_exec_exempted="true" ;; esac - termux_exec__system_linker_exec__log_error_for_level 2 "se_process_context_from_file: '$se_process_context'" - else - # If '/proc/self/attr/current' is not accessible, - # then SeLinux may not be supported on the device. - termux_exec__system_linker_exec__log_error_for_level 2 "Failed to find or read '/proc/self/attr/current' to read se_process_context value" - termux_exec__system_linker_exec__log_error_for_level 2 "se_process_context_available: 'false'" - app_data_file_exec_exempted="true" fi termux_exec__system_linker_exec__log_error_for_level 2 "app_data_file_exec_exempted: '$app_data_file_exec_exempted'" From 3300bfba6445e61bb647bd911467ba40c9e851d9 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 23 Mar 2025 22:54:11 +0500 Subject: [PATCH 28/47] Fixed(exec-intercept): Do not open system executables to read file header and do not abort `execve` if file header is not readable for non-system executables due to `ENOENT` In `termux-exec` `1.0`, if `open()` call failed to read header, then `execve` was still continued. The behaviour was changed in `v2.0.0` via 1fac1073 where `execve` was aborted if `open()` failed. - https://github.com/termux/termux-exec-package/blob/v1.0/termux-exec.c#L97-L98 https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c#L329 - https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c#L189 Now we make 2 changes. 1. We do not even attempt to `open()` system executables to read their file header, as interpreter exec should not be required for such files, as their would normally have `#!/system/bin/sh` as interpreter, which doesn't need fixing like `#/usr/bin/sh` or `#!/bin/sh` would for executables under Termux app data directory. Additionally, on some devices, attempting to open system executables with `open()` will fail with `No such file or directory (ENOENT)` error, even though execution of such files is allowed and `access(X_OK)` succeeds. 2. If executable is not a system executable and reading the file header still failed with `No such file or directory (ENOENT)` error, then do not abort `execve` as execution could still be allowed for such files. Abort will still happen for other errors like `Is a directory (EISDIR)`. Closes #31, Closes #32 --- .../ld_preload/direct/exec/ExecIntercept.h | 50 ++++++-- .../ld_preload/direct/exec/ExecIntercept.c | 114 +++++++++++++----- 2 files changed, 124 insertions(+), 40 deletions(-) diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h index a37ebcb..fbddcb3 100644 --- a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h @@ -266,6 +266,31 @@ bool isElfFile(char *header, size_t headerLength); +/** + * Whether an executable path is for a system executable. + * + * Android system executables may exist under the following + * directories depending on the Android version. + * - `/apex` + * - `/odm` + * - `/product` + * - `/sbin` + * - `/system` + * - `/system_ext` + * - `/vendor` + * + * - https://github.com/termux/termux-packages/wiki/Termux-execution-environment#path-environment-variables-exported-by-android + * - https://source.android.com/docs/core/architecture/partitions + * + * @param executablePath The **normalized** executable path to check. + * @return Returns `true` if `executablePath` is a system executable, + * otherwise `false`. + * + */ +bool isSystemExecutable(const char *executablePath); + + + /** * Whether variables in `LD_VARS_TO_UNSET` should be unset before `exec()` * to prevent issues when executing system binaries that are caused @@ -303,16 +328,16 @@ int modifyExecEnv(char *const *envp, char ***newEnvpPointer, /** * Modify the arguments for `execve()`. * - * If `interpreterSet` is set, then `argv[0]` will be set to - * `TermuxFileHeaderInfo.origInterpreterPath`, otherwise the original - * `argv[0]` passed to `execve()` will be preserved. + * If `shouldEnableInterpreterExec` is enabled, then `argv[0]` will be + * set to `TermuxFileHeaderInfo.origInterpreterPath`, otherwise the + * original `argv[0]` passed to `execve()` will be preserved. * * If `shouldEnableSystemLinkerExec` is `true`, then `argv[1]` will be * set to `executablePath` to be executed by the linker. * - * If `interpreterSet` is set, then `TermuxFileHeaderInfo.interpreterArg` - * will be appended if set, followed by the `origExecutablePath` - * passed to `execve()`. + * If `shouldEnableInterpreterExec` is enabled, then + * `TermuxFileHeaderInfo.interpreterArg` will be appended if set, + * followed by the `origExecutablePath` passed to `execve()`. * * Any additional arguments to `execve()` will be appended after this. * @@ -322,8 +347,8 @@ int modifyExecEnv(char *const *envp, char ***newEnvpPointer, * `execve()`. * @param executablePath The **normalized** executable or interpreter * path that will actually be executed. - * @param interpreterSet Whether a interpreter is set in the executable - * file. + * @param shouldEnableInterpreterExec Whether interpreter in executable + * file should be used to execute the path. * @param info The `TermuxFileHeaderInfo` for the executable file. * @param shouldEnableSystemLinkerExec Whether `system_linker_exec` * should be used to execute the path. @@ -333,7 +358,8 @@ int modifyExecEnv(char *const *envp, char ***newEnvpPointer, */ int modifyExecArgs(char *const *argv, const char ***newArgvPointer, const char *origExecutablePath, const char *executablePath, - bool interpreterSet, bool shouldEnableSystemLinkerExec, struct TermuxFileHeaderInfo *info); + bool shouldEnableInterpreterExec, bool shouldEnableSystemLinkerExec, + struct TermuxFileHeaderInfo *info); @@ -352,15 +378,15 @@ int modifyExecArgs(char *const *argv, const char ***newArgvPointer, * path that will actually be executed. * @param processedExecutablePath The **normalized** executable path * that was passed to `execve()`. - * @param interpreterSet Whether a interpreter is set in the executable - * file. + * @param shouldEnableInterpreterExec Whether interpreter in executable + * file should be used to execute the path. * @return Returns `0` `argv[0]` length is `< 128` or running on * Android `>= 6`, otherwise `-1` with errno set to `ENAMETOOLONG` if * buffer overflow would occur.. */ int checkExecArg0BufferOverflow(char *const *argv, const char *executablePath, const char *processedExecutablePath, - bool interpreterSet); + bool shouldEnableInterpreterExec); #ifdef __cplusplus } diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c index 0b695e5..67a5d2f 100644 --- a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c +++ b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c @@ -183,30 +183,61 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], - char header[TERMUX__FILE_HEADER__BUFFER_SIZE]; - ssize_t headerLength = readFileHeader("executable", executablePath, header, sizeof(header)); - if (headerLength < 0) { - return headerLength; - } - - + // NOTE: `TermuxFileHeaderInfo.isElf` state will only be valid if + // header was successfully read and inspected. struct TermuxFileHeaderInfo info = { .interpreterPath = NULL, .interpreterArg = NULL, }; - if (inspectFileHeader(NULL, header, headerLength, &info) != 0) { - return -1; + char header[TERMUX__FILE_HEADER__BUFFER_SIZE]; + bool shouldEnableInterpreterExec = false; + + ssize_t headerLength = -1; + // Only read file header if executable is not a system executable, + // like under `/system` or `/vendor` partitions, etc, as even though + // `access(X_OK)` call may succeed, but `open()` call may fail + // with `No such file or directory (ENOENT)` on some devices. + // For such files, `fexecve()` should not be possible either. + // - `/system/bin/su` if using `KernelSU`. + // - https://github.com/termux/termux-exec-package/issues/31 + // - `/system/bin/*` utilities provided by `toybox`/`toolbox`, + // like `ls` or `getprop` on Samsung devices (S25*). + // - https://github.com/termux/termux-app/issues/4448 + // - https://github.com/termux/termux-exec-package/issues/32 + // - `/system/bin/app_process`. + // - https://github.com/termux/termux-app/issues/4440#issuecomment-2746002438 + if (isSystemExecutable(executablePath)) { + logErrorVVerbose(LOG_TAG, "read_file_header: '0'"); + } else { + logErrorVVerbose(LOG_TAG, "read_file_header: '1'"); + headerLength = readFileHeader("executable", executablePath, header, sizeof(header)); + // Do not continue for errors other than + // `No such file or directory (ENOENT)`, + // like `Is a directory (EISDIR)`. + if (headerLength < 0 && errno != ENOENT) { + return headerLength; + } } - bool interpreterSet = info.interpreterPath != NULL; - if (!info.isElf && !interpreterSet) { - errno = ENOEXEC; - logStrerrorDebug(LOG_TAG, "Not an ELF or no shebang in executable path '%s'", processedExecutablePath); - return -1; + // If executable is not a system executable or failed to read header. + if (headerLength < 0) { + errno = 0; + } else { + if (inspectFileHeader(NULL, header, headerLength, &info) != 0) { + return -1; + } + + if (!info.isElf && info.interpreterPath == NULL) { + errno = ENOEXEC; + logStrerrorDebug(LOG_TAG, "Not an ELF or no shebang in executable path '%s'", processedExecutablePath); + return -1; + } + + shouldEnableInterpreterExec = info.interpreterPath != NULL; } - if (interpreterSet) { + if (shouldEnableInterpreterExec) { executablePath = info.interpreterPath; } @@ -271,13 +302,13 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], - const bool modifyArgs = shouldEnableSystemLinkerExec || interpreterSet; + const bool modifyArgs = shouldEnableInterpreterExec || shouldEnableSystemLinkerExec; logErrorVVerbose(LOG_TAG, "modify_args: '%d'", modifyArgs); const char **newArgv = NULL; if (modifyArgs) { if (modifyExecArgs(argv, &newArgv, origExecutablePath, executablePath, - interpreterSet, shouldEnableSystemLinkerExec, &info) != 0 || + shouldEnableInterpreterExec, shouldEnableSystemLinkerExec, &info) != 0 || newArgv == NULL) { logErrorDebug(LOG_TAG, "Failed to create modified exec args"); free(envTermuxProcSelfExe); @@ -296,7 +327,8 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], #if defined LIBTERMUX_EXEC__NOS__C__EXECVE_CALL__CHECK_ARGV0_BUFFER_OVERFLOW && LIBTERMUX_EXEC__NOS__C__EXECVE_CALL__CHECK_ARGV0_BUFFER_OVERFLOW == 1 - if (checkExecArg0BufferOverflow(argv, executablePath, processedExecutablePath, interpreterSet) != 0) { + if (checkExecArg0BufferOverflow(argv, executablePath, processedExecutablePath, + shouldEnableInterpreterExec) != 0) { return -1; } #endif @@ -328,11 +360,12 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], int readFileHeader(const char *label, const char *executablePath, char *buffer, size_t bufferSize) { + // This may fail, check comment in `execveInterceptInternal()` + // for the `readFileHeader()` call for more info. // - https://man7.org/linux/man-pages/man2/open.2.html int fd = open(executablePath, O_RDONLY); if (fd == -1) { - errno = ENOENT; - logStrerrorDebug(LOG_TAG, "Failed to open %s path '%s'", label, executablePath); + logStrerrorDebug(LOG_TAG, "Failed to open %s path '%s' for file header", label, executablePath); return -1; } @@ -341,7 +374,7 @@ int readFileHeader(const char *label, const char *executablePath, // Ensure read was successful, path could be a directory and EISDIR will be returned. // - https://man7.org/linux/man-pages/man2/read.2.html if (headerLength < 0) { - logStrerrorDebug(LOG_TAG, "Failed to read %s path '%s'", label, executablePath); + logStrerrorDebug(LOG_TAG, "Failed to read %s path '%s' for file header", label, executablePath); return -1; } close(fd); @@ -529,6 +562,23 @@ bool isElfFile(char *header, size_t headerLength) { +bool isSystemExecutable(const char *executablePath) { + if ( + stringStartsWith(executablePath, "/apex/") || + stringStartsWith(executablePath, "/odm/") || + stringStartsWith(executablePath, "/product/") || + stringStartsWith(executablePath, "/sbin/") || + stringStartsWith(executablePath, "/system/") || + stringStartsWith(executablePath, "/system_ext/") || + stringStartsWith(executablePath, "/vendor/")) { + return true; + } + + return false; +} + + + bool shouldUnsetLDVarsFromEnv(bool isNonNativeElf, const char *executablePath) { return isNonNativeElf || (stringStartsWith(executablePath, "/system/") && @@ -614,7 +664,8 @@ int modifyExecEnv(char *const *envp, char ***newEnvpPointer, int modifyExecArgs(char *const *argv, const char ***newArgvPointer, const char *origExecutablePath, const char *executablePath, - bool interpreterSet, bool shouldEnableSystemLinkerExec, struct TermuxFileHeaderInfo *info) { + bool shouldEnableInterpreterExec, bool shouldEnableSystemLinkerExec, + struct TermuxFileHeaderInfo *info) { int argsCount = 0; while (argv[argsCount] != NULL) { argsCount++; @@ -632,7 +683,7 @@ int modifyExecArgs(char *const *argv, const char ***newArgvPointer, int index = 0; - if (interpreterSet) { + if (shouldEnableInterpreterExec) { // Use original interpreter path set in executable file as is. newArgv[index++] = info->origInterpreterPath; } else { @@ -646,7 +697,7 @@ int modifyExecArgs(char *const *argv, const char ***newArgvPointer, } // Add interpreter argument and script path if executing a script with shebang. - if (interpreterSet) { + if (shouldEnableInterpreterExec) { if (info->interpreterArg != NULL) { newArgv[index++] = info->interpreterArg; } @@ -667,7 +718,7 @@ int modifyExecArgs(char *const *argv, const char ***newArgvPointer, int checkExecArg0BufferOverflow(char *const *argv, const char *executablePath, const char *processedExecutablePath, - bool interpreterSet) { + bool shouldEnableInterpreterExec) { logErrorVVerbose(LOG_TAG, "Checking argv[0] buffer overflow"); size_t argv0Length = strlen(argv[0]); @@ -676,11 +727,18 @@ int checkExecArg0BufferOverflow(char *const *argv, if (androidBuildVersionSdk < 23) { bool shouldAbort = false; char* label = ""; - if (interpreterSet) { + if (shouldEnableInterpreterExec) { char interpreterHeader[TERMUX__FILE_HEADER__BUFFER_SIZE]; - ssize_t interpreterHeaderLength = readFileHeader("interpreter", executablePath, interpreterHeader, sizeof(interpreterHeader)); + ssize_t interpreterHeaderLength = readFileHeader("interpreter", + executablePath, interpreterHeader, sizeof(interpreterHeader)); + // While reading file to read header, the `open()` + // call may fail with `No such file or directory (ENOENT)` + // on some devices, so skip checking to abort. + // Check comment in `execveInterceptInternal()` + // for the `readFileHeader()` call for more info. if (interpreterHeaderLength < 0) { - return interpreterHeaderLength; + errno = 0; + return 0; } if (isElfFile(interpreterHeader, interpreterHeaderLength)) { From c1a5bb76e549f0231035b0a802b63c332eb7b9e7 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sun, 23 Mar 2025 22:59:16 +0500 Subject: [PATCH 29/47] Release: 2.2.0 --- Makefile | 2 +- site/pages/en/projects/releases/2/v2.2.0.md | 22 +++++++++++++++++++++ site/pages/en/projects/releases/index.md | 3 ++- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 site/pages/en/projects/releases/2/v2.2.0.md diff --git a/Makefile b/Makefile index 17b0bd3..62508d8 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export TERMUX_EXEC_PKG__VERSION := 2.1.0 +export TERMUX_EXEC_PKG__VERSION := 2.2.0 export TERMUX_EXEC_PKG__ARCH export TERMUX_EXEC_PKG__INSTALL_PREFIX export TERMUX_EXEC_PKG__TESTS__API_LEVEL := diff --git a/site/pages/en/projects/releases/2/v2.2.0.md b/site/pages/en/projects/releases/2/v2.2.0.md new file mode 100644 index 0000000..0b7044d --- /dev/null +++ b/site/pages/en/projects/releases/2/v2.2.0.md @@ -0,0 +1,22 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/2/v2.2.0.html" +--- + +# termux-exec-package v2.2.0 - 2025-03-23 + +## Changelog + +**Commit history:** [`v2.1.0...v2.2.0`](https://github.com/termux/termux-exec-package/compare/v2.1.0...v2.2.0) + +  + + + +### Fixed + +- system-linker-exec: Reattempt fix done in 2f6d9f92 to assume app data file execute restrictions are exempted if `/proc/self/attr/current` is not accessible as SeLinux may not be supported on the device. ([`b566e7ff`](https://github.com/termux/termux-exec-package/commit/b566e7ff)) +- exec-intercept: Do not open system executables to read file header and do not abort `execve` if file header is not readable for non-system executables due to `ENOENT`. ([`3300bfba`](https://github.com/termux/termux-exec-package/commit/3300bfba)) + +--- + +  diff --git a/site/pages/en/projects/releases/index.md b/site/pages/en/projects/releases/index.md index fb54e50..a73152c 100644 --- a/site/pages/en/projects/releases/index.md +++ b/site/pages/en/projects/releases/index.md @@ -6,7 +6,7 @@ page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/index.html This page lists the releases info for [`termux-exec-package`](https://github.com/termux/termux-exec-package). -**The currently latest release of `termux-exec` is [`v2.1.0`](2/v2.1.0.md).** +**The currently latest release of `termux-exec` is [`v2.2.0`](2/v2.2.0.md).** --- @@ -54,6 +54,7 @@ Open a release to view its release notes and/or changelogs. Changelogs are gener - [`v2.0.0`](2/v2.0.0.md) - [`v2.1.0`](2/v2.1.0.md) +- [`v2.2.0`](2/v2.2.0.md) --- From 3f3a69fc3960842c89f7c90114dcb290e4e1afc9 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 24 Mar 2025 10:45:49 +0500 Subject: [PATCH 30/47] Fixed(ExecIntercept): Fix file descriptor leak when reading file header Credits to @drinkcat for reporting it. - https://github.com/termux/termux-exec-package/commit/3300bfba6445e61bb647bd911467ba40c9e851d9#r154209904 --- .../api/termux_exec/ld_preload/direct/exec/ExecIntercept.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c index 67a5d2f..5f42704 100644 --- a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c +++ b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c @@ -371,13 +371,13 @@ int readFileHeader(const char *label, const char *executablePath, ssize_t headerLength = read(fd, buffer, bufferSize - 1); + close(fd); // Ensure read was successful, path could be a directory and EISDIR will be returned. // - https://man7.org/linux/man-pages/man2/read.2.html if (headerLength < 0) { logStrerrorDebug(LOG_TAG, "Failed to read %s path '%s' for file header", label, executablePath); return -1; } - close(fd); return headerLength; } From f9deee2c3787ba2fe03a9d4e760429e3297fdbfa Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Mon, 24 Mar 2025 10:48:40 +0500 Subject: [PATCH 31/47] Changed: Use standardized format for version string output as per the ` version= org= project=` format --- Makefile | 4 ++++ .../api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in | 2 +- .../termux_exec/ld_preload/termux-exec-system-linker-exec.in | 2 +- app/main/tests/termux-exec-tests.in | 2 +- .../ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c | 5 ++++- .../ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c | 5 ++++- 6 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index 62508d8..cc07432 100644 --- a/Makefile +++ b/Makefile @@ -6,6 +6,8 @@ export TERMUX_EXEC_PKG__TESTS__API_LEVEL := export TERMUX__NAME := Termux# Default value: `Termux` export TERMUX__LNAME := termux# Default value: `termux` +export TERMUX__REPOS_HOST_ORG_NAME := termux# Default value: `termux` + export TERMUX_APP__NAME := Termux# Default value: `Termux` export TERMUX_APP__PACKAGE_NAME := com.termux# Default value: `com.termux` export TERMUX_APP__DATA_DIR := /data/data/$(TERMUX_APP__PACKAGE_NAME)# Default value: `/data/data/com.termux` @@ -93,6 +95,7 @@ export override TERMUX__CONSTANTS__MACRO_FLAGS := \ -DTERMUX_EXEC_PKG__VERSION=\"$(TERMUX_EXEC_PKG__VERSION)\" \ -DTERMUX__NAME=\"$(TERMUX__NAME)\" \ -DTERMUX__LNAME=\"$(TERMUX__LNAME)\" \ + -DTERMUX__REPOS_HOST_ORG_NAME=\"$(TERMUX__REPOS_HOST_ORG_NAME)\" \ -DTERMUX_APP__DATA_DIR=\"$(TERMUX_APP__DATA_DIR)\" \ -DTERMUX__ROOTFS=\"$(TERMUX__ROOTFS)\" \ -DTERMUX__PREFIX=\"$(TERMUX__PREFIX)\" \ @@ -107,6 +110,7 @@ export override TERMUX__CONSTANTS__SED_ARGS := \ -e "s%[@]TERMUX_EXEC_PKG__VERSION[@]%$(TERMUX_EXEC_PKG__VERSION)%g" \ -e "s%[@]TERMUX_EXEC_PKG__ARCH[@]%$(TERMUX_EXEC_PKG__ARCH)%g" \ -e "s%[@]TERMUX__LNAME[@]%$(TERMUX__LNAME)%g" \ + -e "s%[@]TERMUX__REPOS_HOST_ORG_NAME[@]%$(TERMUX__REPOS_HOST_ORG_NAME)%g" \ -e "s%[@]TERMUX_APP__NAME[@]%$(TERMUX_APP__NAME)%g" \ -e "s%[@]TERMUX_APP__PACKAGE_NAME[@]%$(TERMUX_APP__PACKAGE_NAME)%g" \ -e "s%[@]TERMUX_APP__DATA_DIR[@]%$(TERMUX_APP__DATA_DIR)%g" \ diff --git a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in index ec19976..d695762 100644 --- a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in +++ b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in @@ -40,7 +40,7 @@ termux_exec__ld_preload_lib__main() { termux_exec__ld_preload_lib__show_help || return $? return 0 elif [ "${1:-}" = "--version" ]; then - echo "@TERMUX_EXEC_PKG__VERSION@" || return $? + echo "termux-exec-ld-preload-lib version=@TERMUX_EXEC_PKG__VERSION@ org=@TERMUX__REPOS_HOST_ORG_NAME@ project=termux-exec-package"; return $? return 0 fi diff --git a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in index 7bbd421..98eb0b2 100644 --- a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in +++ b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in @@ -32,7 +32,7 @@ termux_exec__system_linker_exec__main() { termux_exec__system_linker_exec__show_help || return $? return 0 elif [ "${1:-}" = "--version" ]; then - echo "@TERMUX_EXEC_PKG__VERSION@" || return $? + echo "termux-exec-system-linker-exec version=@TERMUX_EXEC_PKG__VERSION@ org=@TERMUX__REPOS_HOST_ORG_NAME@ project=termux-exec-package"; return $? return 0 fi diff --git a/app/main/tests/termux-exec-tests.in b/app/main/tests/termux-exec-tests.in index fe06a58..7b4f345 100644 --- a/app/main/tests/termux-exec-tests.in +++ b/app/main/tests/termux-exec-tests.in @@ -609,7 +609,7 @@ termux_exec__tests__process_script_arguments() { ;; version) TERMUX_EXEC__TESTS__COMMAND_TYPE_NOOP="true" - echo "@TERMUX_EXEC_PKG__VERSION@"; return $? + echo "termux-exec-tests version=@TERMUX_EXEC_PKG__VERSION@ org=@TERMUX__REPOS_HOST_ORG_NAME@ project=termux-exec-package"; return $? ;; quiet) TERMUX_EXEC__TESTS__LOG_LEVEL=0 diff --git a/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c b/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c index cc48f22..0384d39 100644 --- a/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c +++ b/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c @@ -19,10 +19,13 @@ * `nm --demangle --dynamic --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/lib/libtermux-exec-direct-ld-preload.so`. */ +#define LIBTERMUX_EXEC_DIRECT_LD_PRELOAD__VERSION_NAME TERMUX_EXEC_PKG__VERSION +#define LIBTERMUX_EXEC_DIRECT_LD_PRELOAD__VERSION_STRING "libtermux-exec-direct-ld-preload version=" LIBTERMUX_EXEC_DIRECT_LD_PRELOAD__VERSION_NAME " org=" TERMUX__REPOS_HOST_ORG_NAME " project=termux-exec-package" + void termuxExec_directLdPreload_initProcess() { - termuxExec_process_initProcess(TERMUX_EXEC_PKG__VERSION "+direct-ld-preload", NULL); + termuxExec_process_initProcess(LIBTERMUX_EXEC_DIRECT_LD_PRELOAD__VERSION_STRING, NULL); } diff --git a/app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c b/app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c index 1a90e7d..a7fa28b 100644 --- a/app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c +++ b/app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c @@ -19,10 +19,13 @@ * `nm --demangle --dynamic --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/lib/libtermux-exec-linker-ld-preload.so`. */ +#define LIBTERMUX_EXEC_LINKER_LD_PRELOAD__VERSION_NAME TERMUX_EXEC_PKG__VERSION +#define LIBTERMUX_EXEC_LINKER_LD_PRELOAD__VERSION_STRING "libtermux-exec-linker-ld-preload version=" TERMUX_EXEC_PKG__VERSION " org=" TERMUX__REPOS_HOST_ORG_NAME " project=termux-exec-package" + void termuxExec_linkerLdPreload_initProcess() { - termuxExec_process_initProcess(TERMUX_EXEC_PKG__VERSION "+linker-ld-preload", NULL); + termuxExec_process_initProcess(LIBTERMUX_EXEC_LINKER_LD_PRELOAD__VERSION_STRING, NULL); } From 0f0866e7ad8a978d45801f6373ba385a8a560f70 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Wed, 26 Mar 2025 21:50:24 +0500 Subject: [PATCH 32/47] Fixed(ExecIntercept_RuntimeBinaryTests): Do not log failure logs to stdout of parent and log them to original stderr of child, otherwise they would get captured in `output` of parent and compared with `output_regex` --- .../direct/exec/ExecIntercept_RuntimeBinaryTests.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c index 3cccaa4..b410663 100644 --- a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c +++ b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c @@ -114,6 +114,13 @@ FEXECVE_CALL_IMPL() if (info.isChild) { \ int actualReturnValue; \ execWrapper(variant, name, envp, __VA_ARGS__); \ + /* Do not log failure logs to stdout of parent and log them to original stderr of child, \ + otherwise they would get captured in `output` of parent and compared with \ + `output_regex`. */ \ + if (dup2(info.stderrFd, STDERR_FILENO) == -1) { \ + logStrerror(LOG_TAG, "Failed to restore child stderr"); \ + exitForkWithError(&info, 1); \ + } \ int actualErrno = errno; \ int testFailed = 0; \ if (actualReturnValue != expectedReturnValue) { \ From 4064813cc1d1b9d0b0113ea6b636396038ee3120 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Wed, 26 Mar 2025 21:51:01 +0500 Subject: [PATCH 33/47] Fixed(ExecIntercept_RuntimeBinaryTests): Remove invalid `FEXECVE_SUPPORTED` define --- .../ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c index b410663..be70ecc 100644 --- a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c +++ b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c @@ -25,10 +25,6 @@ void ExecIntercept_runTests() { #define FEXECVE_SUPPORTED 1 #endif -#ifndef __ANDROID__ -#define FEXECVE_SUPPORTED 1 -#endif - #if defined FEXECVE_SUPPORTED #define FEXECVE_CALL_IMPL() \ int fexecveCall(int fd, char *const *argv, char *const *envp) { \ From 9e0f4b2f6b57a1326c081f4efd683a036b864121 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Wed, 26 Mar 2025 21:52:33 +0500 Subject: [PATCH 34/47] Added(ExecIntercept_RuntimeBinaryTests): Allow not running `ExecLP`, `ExecVP` and `ExecVPE` tests by setting `file` to `NULL` --- .../exec/ExecIntercept_RuntimeBinaryTests.c | 30 +++++++++++-------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c index be70ecc..942cf14 100644 --- a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c +++ b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c @@ -207,10 +207,12 @@ FEXECVE_CALL_IMPL() ExecL, path, NULL, path, __VA_ARGS__); \ } \ { \ - /* ExecLP */ \ - runExecTest(testName, expectedReturnValueP, expectedErrnoP, \ - expectedExitCodeP, expectedOutputRegexP, expectedOutputRegexFlags, \ - ExecLP, file, NULL, file, __VA_ARGS__); \ + if (file != NULL) { \ + /* ExecLP */ \ + runExecTest(testName, expectedReturnValueP, expectedErrnoP, \ + expectedExitCodeP, expectedOutputRegexP, expectedOutputRegexFlags, \ + ExecLP, file, NULL, file, __VA_ARGS__); \ + } \ } \ { \ /* ExecLE */ \ @@ -227,16 +229,20 @@ FEXECVE_CALL_IMPL() ExecV, path, NULL, path, __VA_ARGS__); \ } \ { \ - /* ExecVP */ \ - runExecTest(testName, expectedReturnValueP, expectedErrnoP, \ - expectedExitCodeP, expectedOutputRegexP, expectedOutputRegexFlags, \ - ExecVP, file, NULL, file, __VA_ARGS__); \ + if (file != NULL) { \ + /* ExecVP */ \ + runExecTest(testName, expectedReturnValueP, expectedErrnoP, \ + expectedExitCodeP, expectedOutputRegexP, expectedOutputRegexFlags, \ + ExecVP, file, NULL, file, __VA_ARGS__); \ + } \ } \ { \ - /* ExecVPE */ \ - runExecTest(testName, expectedReturnValueP, expectedErrnoP, \ - expectedExitCodeP, expectedOutputRegexP, expectedOutputRegexFlags, \ - ExecVPE, file, envp, file, __VA_ARGS__); \ + if (file != NULL) { \ + /* ExecVPE */ \ + runExecTest(testName, expectedReturnValueP, expectedErrnoP, \ + expectedExitCodeP, expectedOutputRegexP, expectedOutputRegexFlags, \ + ExecVPE, file, envp, file, __VA_ARGS__); \ + } \ } \ \ \ From 4ac33b31772e27f0922f17cb1767241fc31bdc86 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Wed, 26 Mar 2025 21:54:23 +0500 Subject: [PATCH 35/47] Added(ExecIntercept_RuntimeBinaryTests): Allow not comparing exit code by setting `expectedExitCode` to `-1` This is needed for running specific commands whose exit code can vary on different Android versions, like when getting help output of system executables. --- .../ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c index 942cf14..09c57ee 100644 --- a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c +++ b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c @@ -160,7 +160,7 @@ FEXECVE_CALL_IMPL() logError(LOG_TAG, "FAILED: '%s' '%s()' test", testName, EXEC_VARIANTS_STR[variant]); \ logError(LOG_TAG, "Expected output_regex does not equal match actual output"); \ testFailed=1; \ - } else if (actualExitCode != expectedExitCode) { \ + } else if ((expectedExitCode != -1 && actualExitCode != expectedExitCode) || actualExitCode == 100) { \ logError(LOG_TAG, "FAILED: '%s' '%s()' test", testName, EXEC_VARIANTS_STR[variant]); \ logError(LOG_TAG, "Expected exit_code does not equal actual exit_code"); \ testFailed=1; \ From 5b124c17b52818174f831788a80dca93b9ace01b Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Wed, 26 Mar 2025 21:55:53 +0500 Subject: [PATCH 36/47] Added(ExecIntercept_RuntimeBinaryTests): Allow test runner functions to override init function of child process that runs the test --- .../direct/exec/ExecIntercept_RuntimeBinaryTests.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c index 09c57ee..2cc7307 100644 --- a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c +++ b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c @@ -5,6 +5,10 @@ +static void (*onExecTestChildFork)(ForkInfo *info) = initChild; + + + void test__execIntercept(); @@ -100,7 +104,7 @@ FEXECVE_CALL_IMPL() INIT_FORK_INFO(info); \ info.parentLogTag = LOG_TAG; \ info.childLogTag = LOG_TAG; \ - info.onChildFork = initChild; \ + info.onChildFork = onExecTestChildFork; \ int result = forkChild(&info); \ if (result != 0) { \ logError(LOG_TAG, "Unexpected return value for forkChild '%d'", result); \ From 577ffd96da311354843e435d3759c1382ed37afd Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Wed, 26 Mar 2025 22:20:13 +0500 Subject: [PATCH 37/47] Fixed(ExecIntercept): Return `Permission denied (EACCES)` if executing a non-regular file instead of `Is a directory (EISDIR)` for a directory MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Despite `EISDIR` being more appropriate if trying to execute a directory, considering even `bash` and `sh` return the same, `execve` expects `EACCES` as per its man page. > EACCES The file or a script interpreter is not a regular file. - https://man7.org/linux/man-pages/man2/execve.2.html ``` ~ $ TERMUX_EXEC__LOG_LEVEL=4 env . 28077 E termux : TERMUX_EXEC__VERSION: '1:2.1.0+direct-ld-preload' 28077 E termux.exec: <----- execve() intercepted -----> 28077 E termux.exec: executable = '/data/data/com.termux/files/usr/bin/env' 28077 E termux.exec: argv[0] = 'env' 28077 E termux.exec: argv[1] = '.' 28077 E termux.exec: Intercepting execve 28077 E termux.exec: read_file_header: '1' 28077 E termux.ld-preload: system_linker_exec_mode: '1' 28077 E termux.ld-preload: android_build_version_sdk: '30' 28077 E termux.ld-preload: se_process_context_from_file: 'u:r:untrusted_app_27:s0:c212,c256,c512,c768' 28077 E termux.ld-preload: app_data_file_exec_exempted: '1' 28077 E termux.ld-preload: system_linker_exec_enabled: '0' 28077 E termux.exec: unset_ld_vars_from_env: '0' 28077 E termux.exec: modify_env: '0' 28077 E termux.exec: modify_args: '0' 28077 E termux.exec: Calling syscall execve 28077 E termux.exec: executable = '/data/data/com.termux/files/usr/bin/env' 28077 E termux.exec: argv[0] = 'env' 28077 E termux.exec: argv[1] = '.' 28077 E termux : TERMUX_EXEC__VERSION: '1:2.1.0+direct-ld-preload' 28077 E termux.exec: <----- execvp() intercepted -----> 28077 E termux.exec: executable = '/data/data/com.termux/files/usr/bin/.' 28077 E termux.exec: argv[0] = '.' 28077 E termux.exec: Intercepting execve 28077 E termux.exec: normalized_executable: '/data/data/com.termux/files/usr/bin' 28077 E termux.exec: read_file_header: '1' 28077 E termux.exec: Failed to read executable path '/data/data/com.termux/files/usr/bin' for file header: Is a directory 28077 E termux.exec: <----- execve() failed -----> env: ‘.’: Is a directory ~ $ bash -c './' bash: line 1: ./: Is a directory ~ $ /system/bin/sh -c './' /system/bin/sh: ./: can't execute: Is a directory ``` Related commit https://github.com/termux/termux-core-package/commit/1779618b Closes #33 --- .../ld_preload/direct/exec/ExecIntercept.c | 23 +++++++++++++++++-- .../exec/ExecIntercept_RuntimeBinaryTests.c | 18 ++++++++++++--- 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c index 5f42704..6c944be 100644 --- a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c +++ b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c @@ -174,8 +174,11 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], // - https://man7.org/linux/man-pages/man2/access.2.html if (access(executablePath, X_OK) != 0) { // Error out if the file does not exist or is not executable - // fd paths like to a pipes should not be executable either and - // they cannot be seek-ed back if interpreter were to be read. + // fd paths like to a sockets/pipes should not be executable + // either and they cannot be seek-ed back if interpreter were + // to be read. + // `access(X_OK)` will return `EACCES` for socket files, + // which is what `execve()` also returns for non-regular files. // - https://github.com/bminor/bash/blob/bash-5.2/shell.c#L1649 logStrerrorDebug(LOG_TAG, "Failed to access executable path '%s'", processedExecutablePath); return -1; @@ -216,6 +219,12 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], // `No such file or directory (ENOENT)`, // like `Is a directory (EISDIR)`. if (headerLength < 0 && errno != ENOENT) { + // `execve()` is expected to return `EACCES` for + // non-regular files and is also mentioned in its man page. + // > EACCES The file or a script interpreter is not a regular file. + if (errno == EISDIR || errno == ENXIO) { + errno = EACCES; + } return headerLength; } } @@ -247,6 +256,16 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], // Check if `system_linker_exec` is required. int shouldEnableSystemLinkerExecResult = shouldEnableSystemLinkerExecForFile(executablePath); if (shouldEnableSystemLinkerExecResult < 0) { + // `execve()` is expected to return `EACCES` for + // non-regular files and is also mentioned in its man page. + // > EACCES The file or a script interpreter is not a regular file. + // The `getRegularFileFdRealPath()` function called by + // `termuxApp_dataDir_isPathUnder()` will return following + // `errno` for non-regular files. + if (errno == EISDIR || errno == ENXIO) { + errno = EACCES; + } + logStrerrorDebug(LOG_TAG, "Failed to check if system linker exec should be enabled for executable path '%s'", executablePath); return -1; } bool shouldEnableSystemLinkerExec = shouldEnableSystemLinkerExecResult == 0 ? true : false; diff --git a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c index 2cc7307..2e6b7fa 100644 --- a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c +++ b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c @@ -331,10 +331,22 @@ void test__execIntercept() { void test__execIntercept__Basic() { logVVerbose(LOG_TAG, "test__execIntercept__Basic()"); - runAllExecWrappersTest("rootfs", - -1, EISDIR, -1, EISDIR, + runAllExecWrappersTest("android-proc", + -1, EACCES, -1, EACCES, 0, NULL, 0, NULL, 0, - "../../", TERMUX__ROOTFS, environ, + ".", "/proc", environ, + NULL); + + runAllExecWrappersTest("termux-rootfs", + -1, EACCES, -1, EACCES, + 0, NULL, 0, NULL, 0, + ".", TERMUX__ROOTFS, environ, + NULL); + + runAllExecWrappersTest("system-app", + -1, EACCES, -1, EACCES, + 0, NULL, 0, NULL, 0, + ".", "/system/app", environ, NULL); } From 760ff6e4ac9ffd2fadba9a9b038d1d5d6782b92f Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Wed, 26 Mar 2025 22:40:14 +0500 Subject: [PATCH 38/47] Fixed(ExecIntercept): Fix issue on at least on Android `<= 7`, where running commands with empty `$LD_PRELOAD` variable, like `LD_PRELOAD= ` will fail with `CANNOT LINK EXECUTABLE` errors with a random environment variable `` pair or `DT_RUNPATH` directory loaded as a library ``` $ LD_DEBUG=3 LD_PRELOAD= bash CANNOT LINK EXECUTABLE "bash": can't read file "/data/data/com.termux/files/usr/lib": Is a directory [ Android dynamic linker (64-bit) ] W [ LD_PRELOAD set to "" ] I name /data/data/com.termux/files/usr/bin/bash: allocated soinfo @ 0x7f97a24010 W [ Linking "/data/data/com.termux/files/usr/bin/bash" ] I [ "" find_loaded_library_by_soname failed (*candidate=n/a@0x0). Trying harder...] I [ opening ] I name /data/data/com.termux/files/usr/lib: allocated soinfo @ 0x7f97a24240 D DEBUG: can't read file "/data/data/com.termux/files/usr/lib": Is a directory I name /data/data/com.termux/files/usr/lib: freeing soinfo @ 0x7f97a24240 A CANNOT LINK EXECUTABLE "bash": can't read file "/data/data/com.termux/files/usr/lib": Is a directory $ LD_PRELOAD="" /system/bin/sh CANNOT LINK EXECUTABLE "/system/bin/sh": cant read file "/system/lib64": Is a directory ``` ``` $ LD_DEBUG=3 LD_PRELOAD= bash CANNOT LINK EXECUTABLE: library "SHELL=/data/data/com.termux/files/usr/bin/bash" not found page record for 0x7f6384688050 was not found (block_size=64) W [ android linker & debugger ] I name bash: allocated soinfo @ 0x7fa61e09b008 W [ linking bash ] I [ 'SHELL=/data/data/com.termux/files/usr/bin/bash' find_loaded_library_by_soname returned false (*candidate=n/a@0x0). Trying harder...] I [ opening SHELL=/data/data/com.termux/files/usr/bin/bash ] D DEBUG: library "SHELL=/data/data/com.termux/files/usr/bin/bash" not found $ LD_PRELOAD= /system/bin/sh CANNOT LINK EXECUTABLE: library "_=/system/bin/sh" not found $ LD_PRELOAD= /system/bin/pm CANNOT LINK EXECUTABLE: library "_=/system/bin/pm" not found ``` Cause is unknown, but related changes in linker for empty LD_PRELOAD are at: - https://cs.android.com/android/_/android/platform/bionic/+/d799b2bb - https://cs.android.com/android/_/android/platform/bionic/+/44f6e189 Was found locally before on Android 6 while testing, but ignored. Reported again by @robertkirkman at following: - https://github.com/termux/termux-exec-package/issues/31#issuecomment-2751708672 --- .../ld_preload/direct/exec/ExecIntercept.h | 5 +- .../ld_preload/direct/exec/ExecIntercept.c | 42 ++++++++- .../exec/ExecIntercept_UnitBinaryTests.c | 91 +++++++++++++++++-- 3 files changed, 126 insertions(+), 12 deletions(-) diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h index fbddcb3..a091e01 100644 --- a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h @@ -316,12 +316,15 @@ bool shouldUnsetLDVarsFromEnv(bool isNonNativeElf, const char *executablePath); * env variable. * @param unsetLdVarsFromEnv If `true`, then variables in * `LD_VARS_TO_UNSET` will be unset. + * @param unsetLdPreloadFromEnv If `true`, then `ENV__LD_PRELOAD` + * variable will be unset. * @return Returns `0` if successfully modified the env, otherwise * `-1` on failures. Its the callers responsibility to call `free()` * on the `newEnvpPointer` passed. */ int modifyExecEnv(char *const *envp, char ***newEnvpPointer, - char** envTermuxProcSelfExe, bool unsetLdVarsFromEnv); + char** envTermuxProcSelfExe, bool unsetLdVarsFromEnv, + bool unsetLdPreloadFromEnv); diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c index 6c944be..4018321 100644 --- a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c +++ b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c @@ -273,11 +273,39 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], bool modifyEnv = false; + bool unsetLdPreloadFromEnv = false; bool unsetLdVarsFromEnv = shouldUnsetLDVarsFromEnv(info.isNonNativeElf, executablePath); logErrorVVerbose(LOG_TAG, "unset_ld_vars_from_env: '%d'", unsetLdVarsFromEnv); - if (unsetLdVarsFromEnv && areVarsInEnv(envp, LD_VARS_TO_UNSET, LD_VARS_TO_UNSET_SIZE)) { - modifyEnv = true; + if (unsetLdVarsFromEnv) { + // If set to empty or non-empty values. + modifyEnv = areVarsInEnv(envp, LD_VARS_TO_UNSET, LD_VARS_TO_UNSET_SIZE); + } else { + // If set to empty values. + // On older android versions, at least on Android `<= 7`, + // running commands with empty `$LD_PRELOAD` variable, like + // `LD_PRELOAD= ` will fail with `CANNOT LINK EXECUTABLE`, + // errors with a random environment variable `` pair + // or `DT_RUNPATH` directory loaded as a library. + // So if an empty `LD_` variable is found, we unset it from + // the environment. Error has not been noticed to occur with + // empty `$LD_LIBRARY_PATH`, so do not remove that, as that + // could cause problems with user `set -u` shell scripts if + // they are setting empty values and reading in a subprocess. + // ``` + // # Android 7 + // $ LD_PRELOAD="" /system/bin/sh + // CANNOT LINK EXECUTABLE "/system/bin/sh": cant read file "/system/lib64": Is a directory + // # Android 6 + // $ LD_PRELOAD= /system/bin/sh + // CANNOT LINK EXECUTABLE: library "_=/system/bin/sh" not found + // ``` + const char *ld_preload_var[] = { ENV_PREFIX__LD_PRELOAD }; + if (areEmptyVarsInEnv(envp, ld_preload_var, 1)) { + unsetLdPreloadFromEnv = true; + modifyEnv = true; + logErrorVVerbose(LOG_TAG, "unset_ld_preload_from_env: '%d'", unsetLdPreloadFromEnv); + } } @@ -309,7 +337,7 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], char **newEnvp = NULL; if (modifyEnv) { - if (modifyExecEnv(envp, &newEnvp, &envTermuxProcSelfExe, unsetLdVarsFromEnv) != 0 || + if (modifyExecEnv(envp, &newEnvp, &envTermuxProcSelfExe, unsetLdVarsFromEnv, unsetLdPreloadFromEnv) != 0 || newEnvp == NULL) { logErrorDebug(LOG_TAG, "Failed to create modified exec env"); free(envTermuxProcSelfExe); @@ -607,7 +635,8 @@ bool shouldUnsetLDVarsFromEnv(bool isNonNativeElf, const char *executablePath) { } int modifyExecEnv(char *const *envp, char ***newEnvpPointer, - char** envTermuxProcSelfExe, bool unsetLdVarsFromEnv) { + char** envTermuxProcSelfExe, bool unsetLdVarsFromEnv, + bool unsetLdPreloadFromEnv) { int envCount = 0; while (envp[envCount] != NULL) { envCount++; @@ -651,8 +680,13 @@ int modifyExecEnv(char *const *envp, char ***newEnvpPointer, for (int j = 0; j < LD_VARS_TO_UNSET_SIZE; j++) { if (stringStartsWith(envp[i], LD_VARS_TO_UNSET[j])) { keep = false; + break; } } + } else if (unsetLdPreloadFromEnv) { + if (strcmp(envp[i], ENV_PREFIX__LD_PRELOAD) == 0) { + keep = false; + } } if (keep) { diff --git a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c index 2b8e9a4..82f5f57 100644 --- a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c +++ b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c @@ -107,12 +107,14 @@ void test__inspectFileHeader__Basic() { void test__modifyExecEnv__unsetLDVars(); +void test__modifyExecEnv__unsetLDPreload(); void test__modifyExecEnv__setProcSelfExe(); void test__modifyExecEnv() { logVerbose(LOG_TAG, "test__modifyExecEnv()"); test__modifyExecEnv__unsetLDVars(); + test__modifyExecEnv__unsetLDPreload(); test__modifyExecEnv__setProcSelfExe(); int__AEqual(0, errno); @@ -124,7 +126,7 @@ void test__modifyExecEnv__unsetLDVars() { { char *testEnv[] = {"MY_ENV=1", NULL}; char **allocatedEnvp; - modifyExecEnv(testEnv, &allocatedEnvp, NULL, true); + modifyExecEnv(testEnv, &allocatedEnvp, NULL, true, false); state__ATrue(allocatedEnvp != NULL); string__AEqual(allocatedEnvp[0], "MY_ENV=1"); state__ATrue(allocatedEnvp[1] == NULL); @@ -134,7 +136,7 @@ void test__modifyExecEnv__unsetLDVars() { { char *testEnv[] = {"MY_ENV=1", ENV_PREFIX__LD_PRELOAD "a", NULL}; char **allocatedEnvp; - modifyExecEnv(testEnv, &allocatedEnvp, NULL, true); + modifyExecEnv(testEnv, &allocatedEnvp, NULL, true, false); state__ATrue(allocatedEnvp != NULL); string__AEqual(allocatedEnvp[0], "MY_ENV=1"); state__ATrue(allocatedEnvp[1] == NULL); @@ -144,7 +146,7 @@ void test__modifyExecEnv__unsetLDVars() { { char *testEnv[] = {"MY_ENV=1", ENV_PREFIX__LD_PRELOAD "a", "A=B", ENV_PREFIX__LD_LIBRARY_PATH "B", "B=C", NULL}; char **allocatedEnvp; - modifyExecEnv(testEnv, &allocatedEnvp, NULL, true); + modifyExecEnv(testEnv, &allocatedEnvp, NULL, true, false); state__ATrue(allocatedEnvp != NULL); string__AEqual(allocatedEnvp[0], "MY_ENV=1"); string__AEqual(allocatedEnvp[1], "A=B"); @@ -154,6 +156,81 @@ void test__modifyExecEnv__unsetLDVars() { } } +void test__modifyExecEnv__unsetLDPreload() { + logVVerbose(LOG_TAG, "test__modifyExecEnv__unsetLDPreload()"); + + { + char *testEnv[] = {"MY_ENV=1", NULL}; + char **allocatedEnvp; + modifyExecEnv(testEnv, &allocatedEnvp, NULL, false, true); + state__ATrue(allocatedEnvp != NULL); + string__AEqual(allocatedEnvp[0], "MY_ENV=1"); + state__ATrue(allocatedEnvp[1] == NULL); + free(allocatedEnvp); + } + + { + char *testEnv[] = {"MY_ENV=1", ENV_PREFIX__LD_PRELOAD "a", NULL}; + char **allocatedEnvp; + modifyExecEnv(testEnv, &allocatedEnvp, NULL, false, true); + state__ATrue(allocatedEnvp != NULL); + string__AEqual(allocatedEnvp[0], "MY_ENV=1"); + string__AEqual(allocatedEnvp[1], ENV_PREFIX__LD_PRELOAD "a"); + state__ATrue(allocatedEnvp[2] == NULL); + free(allocatedEnvp); + } + + { + char *testEnv[] = {"MY_ENV=1", ENV_PREFIX__LD_PRELOAD, NULL}; + char **allocatedEnvp; + modifyExecEnv(testEnv, &allocatedEnvp, NULL, false, true); + state__ATrue(allocatedEnvp != NULL); + string__AEqual(allocatedEnvp[0], "MY_ENV=1"); + state__ATrue(allocatedEnvp[1] == NULL); + free(allocatedEnvp); + } + + { + char *testEnv[] = {"MY_ENV=1", ENV_PREFIX__LD_PRELOAD "a", "A=B", ENV_PREFIX__LD_LIBRARY_PATH "B", "B=C", NULL}; + char **allocatedEnvp; + modifyExecEnv(testEnv, &allocatedEnvp, NULL, false, true); + state__ATrue(allocatedEnvp != NULL); + string__AEqual(allocatedEnvp[0], "MY_ENV=1"); + string__AEqual(allocatedEnvp[1], ENV_PREFIX__LD_PRELOAD "a"); + string__AEqual(allocatedEnvp[2], "A=B"); + string__AEqual(allocatedEnvp[3], ENV_PREFIX__LD_LIBRARY_PATH "B"); + string__AEqual(allocatedEnvp[4], "B=C"); + state__ATrue(allocatedEnvp[5] == NULL); + free(allocatedEnvp); + } + + { + char *testEnv[] = {"MY_ENV=1", ENV_PREFIX__LD_PRELOAD, "A=B", ENV_PREFIX__LD_LIBRARY_PATH "B", "B=C", NULL}; + char **allocatedEnvp; + modifyExecEnv(testEnv, &allocatedEnvp, NULL, false, true); + state__ATrue(allocatedEnvp != NULL); + string__AEqual(allocatedEnvp[0], "MY_ENV=1"); + string__AEqual(allocatedEnvp[1], "A=B"); + string__AEqual(allocatedEnvp[2], ENV_PREFIX__LD_LIBRARY_PATH "B"); + string__AEqual(allocatedEnvp[3], "B=C"); + state__ATrue(allocatedEnvp[4] == NULL); + free(allocatedEnvp); + } + + { + char *testEnv[] = {"MY_ENV=1", "A=B", ENV_PREFIX__LD_LIBRARY_PATH "B", "B=C", NULL}; + char **allocatedEnvp; + modifyExecEnv(testEnv, &allocatedEnvp, NULL, false, true); + state__ATrue(allocatedEnvp != NULL); + string__AEqual(allocatedEnvp[0], "MY_ENV=1"); + string__AEqual(allocatedEnvp[1], "A=B"); + string__AEqual(allocatedEnvp[2], ENV_PREFIX__LD_LIBRARY_PATH "B"); + string__AEqual(allocatedEnvp[3], "B=C"); + state__ATrue(allocatedEnvp[4] == NULL); + free(allocatedEnvp); + } +} + void test__modifyExecEnv__setProcSelfExe() { logVVerbose(LOG_TAG, "test__modifyExecEnv__setProcSelfExe()"); @@ -163,7 +240,7 @@ void test__modifyExecEnv__setProcSelfExe() { char *testEnv[] = {"MY_ENV=1", NULL}; char **allocatedEnvp; - modifyExecEnv(testEnv, &allocatedEnvp, &termuxProcSelfExe, false); + modifyExecEnv(testEnv, &allocatedEnvp, &termuxProcSelfExe, false, false); state__ATrue(allocatedEnvp != NULL); string__AEqual(allocatedEnvp[0], "MY_ENV=1"); string__AEqual(allocatedEnvp[1], ENV__TERMUX_EXEC__PROC_SELF_EXE "=" TERMUX__PREFIX__BIN_DIR "/bash"); @@ -177,7 +254,7 @@ void test__modifyExecEnv__setProcSelfExe() { char *testEnv[] = {"MY_ENV=1", ENV__TERMUX_EXEC__PROC_SELF_EXE "=" TERMUX__PREFIX__BIN_DIR "/python", NULL}; char **allocatedEnvp; - modifyExecEnv(testEnv, &allocatedEnvp, &termuxProcSelfExe, false); + modifyExecEnv(testEnv, &allocatedEnvp, &termuxProcSelfExe, false, false); state__ATrue(allocatedEnvp != NULL); string__AEqual(allocatedEnvp[0], "MY_ENV=1"); string__AEqual(allocatedEnvp[1], ENV__TERMUX_EXEC__PROC_SELF_EXE "=" TERMUX__PREFIX__BIN_DIR "/bash"); @@ -188,7 +265,7 @@ void test__modifyExecEnv__setProcSelfExe() { { char *testEnv[] = {"MY_ENV=1", NULL}; char **allocatedEnvp; - modifyExecEnv(testEnv, &allocatedEnvp, NULL, false); + modifyExecEnv(testEnv, &allocatedEnvp, NULL, false, false); state__ATrue(allocatedEnvp != NULL); string__AEqual(allocatedEnvp[0], "MY_ENV=1"); state__ATrue(allocatedEnvp[1] == NULL); @@ -198,7 +275,7 @@ void test__modifyExecEnv__setProcSelfExe() { { char *testEnv[] = {"MY_ENV=1", ENV__TERMUX_EXEC__PROC_SELF_EXE "=" TERMUX__PREFIX__BIN_DIR "/python", NULL}; char **allocatedEnvp; - modifyExecEnv(testEnv, &allocatedEnvp, NULL, false); + modifyExecEnv(testEnv, &allocatedEnvp, NULL, false, false); state__ATrue(allocatedEnvp != NULL); string__AEqual(allocatedEnvp[0], "MY_ENV=1"); state__ATrue(allocatedEnvp[1] == NULL); From adf08afe415456449a4f979ef21105943791d8f3 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Wed, 26 Mar 2025 23:09:09 +0500 Subject: [PATCH 39/47] Changed(ExecIntercept): Rename `isSystemExecutable()` function to `isExecutableUnderSystemDir()` --- .../termux_exec/ld_preload/direct/exec/ExecIntercept.h | 8 ++++---- .../termux_exec/ld_preload/direct/exec/ExecIntercept.c | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h index a091e01..78c9d4e 100644 --- a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h +++ b/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h @@ -267,7 +267,7 @@ bool isElfFile(char *header, size_t headerLength); /** - * Whether an executable path is for a system executable. + * Whether an executable path is under a system directory. * * Android system executables may exist under the following * directories depending on the Android version. @@ -283,11 +283,11 @@ bool isElfFile(char *header, size_t headerLength); * - https://source.android.com/docs/core/architecture/partitions * * @param executablePath The **normalized** executable path to check. - * @return Returns `true` if `executablePath` is a system executable, - * otherwise `false`. + * @return Returns `true` if `executablePath` is under a system + * directory, otherwise `false`. * */ -bool isSystemExecutable(const char *executablePath); +bool isExecutableUnderSystemDir(const char *executablePath); diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c index 4018321..b156c18 100644 --- a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c +++ b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c @@ -210,7 +210,7 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], // - https://github.com/termux/termux-exec-package/issues/32 // - `/system/bin/app_process`. // - https://github.com/termux/termux-app/issues/4440#issuecomment-2746002438 - if (isSystemExecutable(executablePath)) { + if (isExecutableUnderSystemDir(executablePath)) { logErrorVVerbose(LOG_TAG, "read_file_header: '0'"); } else { logErrorVVerbose(LOG_TAG, "read_file_header: '1'"); @@ -609,7 +609,7 @@ bool isElfFile(char *header, size_t headerLength) { -bool isSystemExecutable(const char *executablePath) { +bool isExecutableUnderSystemDir(const char *executablePath) { if ( stringStartsWith(executablePath, "/apex/") || stringStartsWith(executablePath, "/odm/") || From 4477568e3e244dac47e1ebb55d942c75858e6c34 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 27 Mar 2025 02:26:22 +0500 Subject: [PATCH 40/47] Fixed(ExecIntercept): Fix `LD_VARS` not being unset for system executables if executed with `fexecve()` or fd path, and their file header being read instead of skipped as per 3300bfba File descriptor executable paths are in the format `/proc/self/fd/` or `/proc//fd/`, normally for `fexecve()`, but can be passed manually by callers. For such paths, checks would fail for if the path is under system directories with `isExecutableUnderSystemDir()` or if `LD_VARS` should be unset with `shouldUnsetLDVarsFromEnv()`, as path would start with `/proc` instead of one of the system directories. Now we find the real path before any checks are done by calling `readlink()` via `getRegularFileFdRealPath()` on the fd path. Note that `fexecve()` is only supported for Android `>= 9` and `CANNOT LINK EXECUTABLE` errors due to `LD_VARS` being set like `LD_LIBRARY_PATH=/data/data/com.termux/files/usr/lib` when calling system binaries only seem to occur on older Android versions. So we simulate `fexecve()` for tests by opening a system executable, generating fd path manually and then executing it. Following are examples of errors caused by `LD_LIBRARY_PATH` being set. Termux app only exports it for Android `5` and `6` though. ``` $ LD_PRELOAD= bash $ LD_LIBRARY_PATH=/data/data/com.termux/files/usr/lib PATH=/system/bin /system/bin/{am,pm} CANNOT LINK EXECUTABLE: could not load library "libandroid_runtime.so" needed by "app_process"; caused by could not load library "libhwui.so" needed by "libandroid_runtime.so"; caused by could not load library "libRS.so" needed by "libhwui.so"; caused by could not load library "libbcc.so" needed by "libRS.so"; caused by could not load library "libbcinfo.so" needed by "libbcc.so"; caused by cannot locate symbol "_ZNK4llvm6MDNode10getOperandEj" referenced by "libbcinfo.so"... $ LD_LIBRARY_PATH=/data/data/com.termux/files/usr/lib PATH=/system/bin /system/bin/dalvikvm Aborted dlopen("libjavacore.so", RTLD_LAZY) failed: dlopen failed: cannot locate symbol "bn_expand2" referenced by "libjavacore.so"... LD_LIBRARY_PATH=/data/data/com.termux/files/usr/lib PATH=/system/bin /system/bin/{am,pm} CANNOT LINK EXECUTABLE: cannot locate symbol "u_charMirror_55" referenced by "/system/lib64/libandroid_runtime.so"... $ LD_LIBRARY_PATH=/data/data/com.termux/files/usr/lib PATH=/system/bin /system/bin/dalvikvm Aborted art/runtime/runtime.cc:1149] LoadNativeLibrary failed for "libjavacore.so": dlopen failed: cannot locate symbol "_ZTVN6icu_5513UnicodeStringE" referenced by "/system/lib64/libjavacore.so"... ~ $ LD_LIBRARY_PATH=/data/data/com.termux/files/usr/lib PATH=/system/bin /system/bin/{am,pm} CANNOT LINK EXECUTABLE "app_process": cannot locate symbol "glTexGenxvOES" referenced by "/system/lib64/libandroid_runtime.so"... Aborted $ LD_LIBRARY_PATH=/data/data/com.termux/files/usr/lib PATH=/system/bin /system/bin/dalvikvm Failed to initialize JNI invocation API from (null) Failed to dlopen libart.so: dlopen failed: cannot locate symbol "XzUnpacker_Construct" referenced by "/system/lib64/libunwind.so"... ``` --- .../ld_preload/direct/exec/ExecIntercept.c | 41 +++++- .../exec/ExecIntercept_RuntimeBinaryTests.c | 131 ++++++++++++++++++ 2 files changed, 171 insertions(+), 1 deletion(-) diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c index b156c18..f47d552 100644 --- a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c +++ b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c @@ -91,6 +91,12 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], // For instance, `$TERMUX__PREFIX/bin/ls` is a symlink to `$TERMUX__PREFIX/bin/coreutils`, // but we need to execute `$TERMUX__PREFIX/bin/ls` `/system/bin/linker $TERMUX__PREFIX/bin/ls` // so that coreutils knows what to execute. + // - For an fd path, like `/proc/self/fd/` or `/proc//fd/`, + // normally for the `fexecve()` call, we find its real path, + // so that so if its not under a system directory. then its file + // header is always read. And if it is under a system directory, + // then `LD_VARS_TO_UNSET` are unset properly as per + // `unsetLdVarsFromEnv` or `unsetLdPreloadFromEnv`. // - For an absolute path, we need to normalize first so that an // unnormalized prefix like `/usr/./bin` is replaced with `/usr/bin` // so that `termuxPrefixPath()` can successfully match it to @@ -117,7 +123,24 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], char processedExecutablePathBuffer[PATH_MAX]; - if (executablePath[0] == '/') { + if (isFdPath(executablePath)) { + executablePath = getRegularFileFdRealPath(LOG_TAG, executablePath, + processedExecutablePathBuffer, sizeof(processedExecutablePathBuffer)); + if (executablePath == NULL) { + // `execve()` is expected to return `EACCES` for + // non-regular files and is also mentioned in its man page. + // > EACCES The file or a script interpreter is not a regular file. + // The `getRegularFileFdRealPath()` function will return + // following `errno` for non-regular files. + if (errno == EISDIR || errno == ENXIO) { + errno = EACCES; + } + logStrerrorDebug(LOG_TAG, "Failed to get real path for fd executable path '%s'", origExecutablePath); + return -1; + } + + logErrorVVerbose(LOG_TAG, "real_executable: '%s'", executablePath); + } else if (executablePath[0] == '/') { // If path is absolute, then normalize first and then replace termux prefix. executablePath = normalizePath(executablePathBuffer, false, true); if (executablePath == NULL) { @@ -210,6 +233,22 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], // - https://github.com/termux/termux-exec-package/issues/32 // - `/system/bin/app_process`. // - https://github.com/termux/termux-app/issues/4440#issuecomment-2746002438 + // + // A script running with `system_linker_exec` must always be run + // with its interpreter in the shebang instead of directly, + // otherwise execution will fail with errors like following. + // The `system_linker_exec` is only used if path is under + // Termux app data directory, and we skip reading file header only + // if its under a system directory. Since app data directories + // are either under `/data` or `/mnt`, there is no conflict with + // system directories, and so file headers for files under app + // data directories should always be read. + // error: "/data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh" \ + // is too small to be an ELF executable: only found 52 bytes + // - https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/linker/linker_phdr.cpp;l=216 + // error: "/data/data/com.termux/files/usr/bin/login" \ + // has bad ELF magic: 23212f64 + // - https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/linker/linker_phdr.cpp;l=234 if (isExecutableUnderSystemDir(executablePath)) { logErrorVVerbose(LOG_TAG, "read_file_header: '0'"); } else { diff --git a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c index 2e6b7fa..368dd89 100644 --- a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c +++ b/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c @@ -275,6 +275,7 @@ FEXECVE_CALL_IMPL() void test__execIntercept__Basic(); +void test__execIntercept__AndroidTools(char* envCurrentPath); void test__execIntercept__Files(const char* termuxExec_tests_testsPath, const char* currentPath, char* envCurrentPath); void test__execIntercept__PackageManager(); @@ -317,6 +318,7 @@ void test__execIntercept() { // - https://cs.android.com/android/platform/superproject/+/android-14.0.0_r18:bionic/tests/utils.h;l=200 test__execIntercept__Basic(); + test__execIntercept__AndroidTools(envCurrentPath); test__execIntercept__Files(termuxExec_tests_testsPath, currentPath, envCurrentPath); test__execIntercept__PackageManager(); @@ -328,6 +330,14 @@ void test__execIntercept() { +static void test__execIntercept_initChildWithRedirectedFd(ForkInfo *info) { + initChild(info); + + info->redirectChildStdinToDevNull = true; +} + + + void test__execIntercept__Basic() { logVVerbose(LOG_TAG, "test__execIntercept__Basic()"); @@ -350,6 +360,127 @@ void test__execIntercept__Basic() { NULL); } +void test__execIntercept__AndroidTools(char* envCurrentPath) { + logVVerbose(LOG_TAG, "test__execIntercept__AndroidTools()"); + + onExecTestChildFork = test__execIntercept_initChildWithRedirectedFd; + + // execlp(), execvp() and execvpe() search for file to be executed in $PATH, + // so set it to `/system/bin` directory. + char* envNewPath = NULL; + + if (asprintf(&envNewPath, "%s%s", ENV_PREFIX__PATH, "/apex/com.android.art/bin:/system/bin") == -1) { + errno = ENOMEM; + logStrerrorDebug(LOG_TAG, "asprintf failed for new '%s%s'", ENV_PREFIX__PATH, "/apex/com.android.art/bin:/system/bin"); + exit(1); + } + + putenv(envNewPath); + + + // On Android `14`, `/system/bin/am` can only be run with `adb` + // and `root` users and will normally have no output and exit code + // will be `255`. + // - https://github.com/termux/TermuxAm/commit/8a08e9bd + if (android_buildVersionSdk_get() < 34) { + char* amPath = "/system/bin/am"; + + runAllExecWrappersTest("android-am", + 0, 0, 0, 0, + -1, "^.* specifications.*", -1, "^.* specifications.*", REG_EXTENDED | REG_ICASE, + "am", amPath, environ, + NULL); + + + // Simulate `fexecve()`, especially for Android `< 9`. + // This will also test `LD_LIBRARY_PATH` being unset on older + // Android versions to prevent `CANNOT LINK EXECUTABLE` errors. + int amFd = open(amPath, 0); + if (amFd == -1) { + logStrerror(LOG_TAG, "open() call failed for am at path '%s'", amPath); + exit(1); + } + + char amFdPath[40]; + snprintf(amFdPath, sizeof(amFdPath), "/proc/self/fd/%d", amFd); + + logVVerbose(LOG_TAG, "%s_exec()", "android-am-fd"); \ + runExecTest("android-am-fd", + 0, 0, + -1, "^.* specifications.*", REG_EXTENDED | REG_ICASE, + ExecVE, amFdPath, environ, amPath, NULL); + } + + + // On Android `< 9`, `/system/bin/pm` did not have a `#!/system/bin/sh` + // interpreter and `execve` would fail with `Exec format error (ENOEXEC)`. + // So interpret the `pm` script with `sh` instead of executing it. + // - https://cs.android.com/android/_/android/platform/frameworks/base/+/6c168885 + char* pmPath = "/system/bin/pm"; + if (android_buildVersionSdk_get() < 29) { + runAllExecWrappersTest("android-pm", + 0, 0, 0, 0, + -1, "^.*path to the \\.apk.*", -1, "^.*path to the \\.apk.*", REG_EXTENDED | REG_ICASE, + NULL, "/system/bin/sh", environ, + pmPath, NULL); + + } else { + runAllExecWrappersTest("android-pm", + 0, 0, 0, 0, + -1, "^.*path to the \\.apk.*", -1, "^.*path to the \\.apk.*", REG_EXTENDED | REG_ICASE, + "pm", pmPath, environ, + NULL); + } + + + // On Android `>= 13`, `dalvikvm` is part of Android Runtime APEX module. + // - https://cs.android.com/android/_/android/platform/art/+/38a938e2 + // - https://cs.android.com/android/_/android/platform/bionic/+/6d5277db + char* dalvikvmPath; + if (access("/apex/com.android.art/bin/dalvikvm", X_OK) == 0) { + dalvikvmPath = "/apex/com.android.art/bin/dalvikvm"; + } else { + dalvikvmPath = "/system/bin/dalvikvm"; + } + + char* dalvikvmOutputRegex = "^.*((-classpath ((classpath)|(\\{string value\\})))|(Class name required)).*"; + + // The `-h` flag does not print help on Android `7`, but Android `6` does. + runAllExecWrappersTest("android-dalvikvm", + 0, 0, 0, 0, + -1, dalvikvmOutputRegex, -1, dalvikvmOutputRegex, REG_EXTENDED | REG_ICASE, + "dalvikvm", dalvikvmPath, environ, + "-h", NULL); + + + // Simulate `fexecve()`, especially for Android `< 9`. + // This will also test `LD_LIBRARY_PATH` being unset on older + // Android versions to prevent `CANNOT LINK EXECUTABLE` errors. + int dalvikvmFd = open(dalvikvmPath, 0); + if (dalvikvmFd == -1) { + logStrerror(LOG_TAG, "open() call failed for dalvikvm at path '%s'", dalvikvmPath); + exit(1); + } + + char dalvikvmFdPath[40]; + snprintf(dalvikvmFdPath, sizeof(dalvikvmFdPath), "/proc/self/fd/%d", dalvikvmFd); + + logVVerbose(LOG_TAG, "%s_exec()", "android-dalvikvm-fd"); \ + runExecTest("android-dalvikvm-fd", + 0, 0, + -1, dalvikvmOutputRegex, REG_EXTENDED | REG_ICASE, + ExecVE, dalvikvmFdPath, environ, dalvikvmPath, "-h", NULL); + + + putenv(envCurrentPath); + + + free(envNewPath); + + onExecTestChildFork = initChild; + +} + void test__execIntercept__Files(const char* termuxExec_tests_testsPath, const char* currentPath, char* envCurrentPath) { logVVerbose(LOG_TAG, "test__execIntercept__Files()"); From 07b0d8266f7891a084eb579120e6a0ec9bf1a985 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 27 Mar 2025 02:52:26 +0500 Subject: [PATCH 41/47] Release: 2.3.0 --- Makefile | 2 +- site/pages/en/projects/releases/2/v2.3.0.md | 37 +++++++++++++++++++++ site/pages/en/projects/releases/index.md | 3 +- 3 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 site/pages/en/projects/releases/2/v2.3.0.md diff --git a/Makefile b/Makefile index cc07432..dbc4495 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export TERMUX_EXEC_PKG__VERSION := 2.2.0 +export TERMUX_EXEC_PKG__VERSION := 2.3.0 export TERMUX_EXEC_PKG__ARCH export TERMUX_EXEC_PKG__INSTALL_PREFIX export TERMUX_EXEC_PKG__TESTS__API_LEVEL := diff --git a/site/pages/en/projects/releases/2/v2.3.0.md b/site/pages/en/projects/releases/2/v2.3.0.md new file mode 100644 index 0000000..02eac63 --- /dev/null +++ b/site/pages/en/projects/releases/2/v2.3.0.md @@ -0,0 +1,37 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/2/v2.3.0.html" +--- + +# termux-exec-package v2.3.0 - 2025-03-26 + +## Changelog + +**Commit history:** [`v2.2.0...v2.3.0`](https://github.com/termux/termux-exec-package/compare/v2.2.0...v2.3.0) + +  + + + +### Changed + +- Use standardized format for version string output as per the ` version= org= project=` format. ([`f9deee2c`](https://github.com/termux/termux-exec-package/commit/f9deee2c)) + +##   + +  + + + +### Fixed + +- ExecIntercept: Fix `LD_VARS` not being unset for system executables if executed with `fexecve()` or fd path, and their file header being read instead of skipped as per ([`3300bfba`](https://github.com/termux/termux-exec-package/commit/3300bfba)). ([`4477568e`](https://github.com/termux/termux-exec-package/commit/4477568e)) + +- ExecIntercept: Fix issue on at least on Android `<= 7`, where running commands with empty `$LD_PRELOAD` variable, like `LD_PRELOAD= ` will fail with `CANNOT LINK EXECUTABLE` errors with a random environment variable `` pair or `DT_RUNPATH` directory loaded as a library. ([`760ff6e4`](https://github.com/termux/termux-exec-package/commit/760ff6e4)) + +- ExecIntercept: Return `Permission denied (EACCES)` if executing a non-regular file instead of `Is a directory (EISDIR)` for a directory. Related commit https://github.com/termux/termux-core-package/commit/1779618b. Closes [#33](https://github.com/termux/termux-exec-package/issues/33). ([`577ffd96`](https://github.com/termux/termux-exec-package/commit/577ffd96)) + +- ExecIntercept_RuntimeBinaryTests: Do not log failure logs to stdout of parent and log them to original stderr of child, otherwise they would get captured in `output` of parent and compared with `output_regex`. ([`0f0866e7`](https://github.com/termux/termux-exec-package/commit/0f0866e7)) + +--- + +  diff --git a/site/pages/en/projects/releases/index.md b/site/pages/en/projects/releases/index.md index a73152c..d8cb919 100644 --- a/site/pages/en/projects/releases/index.md +++ b/site/pages/en/projects/releases/index.md @@ -6,7 +6,7 @@ page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/index.html This page lists the releases info for [`termux-exec-package`](https://github.com/termux/termux-exec-package). -**The currently latest release of `termux-exec` is [`v2.2.0`](2/v2.2.0.md).** +**The currently latest release of `termux-exec` is [`v2.3.0`](2/v2.3.0.md).** --- @@ -55,6 +55,7 @@ Open a release to view its release notes and/or changelogs. Changelogs are gener - [`v2.0.0`](2/v2.0.0.md) - [`v2.1.0`](2/v2.1.0.md) - [`v2.2.0`](2/v2.2.0.md) +- [`v2.3.0`](2/v2.3.0.md) --- From 8885c26bdeea90cb4954e64735d76e592dfcf607 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 29 Mar 2025 02:50:31 +0500 Subject: [PATCH 42/47] Added: Add link to 5ea25ee3 for initial implementation of `system_linker_exec` in `v2.0.0` release notes Seems like changes to `src/termux-exec.c` accidentally got removed while rebasing 2fe47750 due to file path changes. So add link to older commit that had the file changes for credits and historical reason. 5ea25ee3 was modified version of https://github.com/termux/termux-exec-package/commit/38a51829 and https://github.com/termux/termux-exec-package/commit/ecad16fd --- site/pages/en/projects/releases/2/v2.0.0.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/pages/en/projects/releases/2/v2.0.0.md b/site/pages/en/projects/releases/2/v2.0.0.md index 7283d12..064cecf 100644 --- a/site/pages/en/projects/releases/2/v2.0.0.md +++ b/site/pages/en/projects/releases/2/v2.0.0.md @@ -22,14 +22,14 @@ page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/2/v2.0.0.h   -- Intercept the entire `exec()` family of functions, which is required for Android `14`. Closes termux/termux-packages#18537, termux/termux-app#3758. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) +- Intercept the entire `exec()` family of functions, which is required for Android `14`. Closes termux/termux-packages#18537, termux/termux-app#3758. ([`5ea25ee3`](https://github.com/termux/termux-exec-package/commit/5ea25ee3), [`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073)) - Added the `string` environment variable `TERMUX_EXEC__EXECVE_CALL__INTERCEPT` for whether `execve` would be intercepted for shebang fix or `system_linker_exec`. If set to `enable`, then `execve()` intercept will be enabled. If set to `disable`, then `execve()` intercept will be disabled. The default value is `enable`. The other wrapper functions in the `exec()` family of functions declared in `unistd.h` are always intercepted to solve some other issues on older Android versions, check [`libc/bionic/exec.cpp`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/bionic/exec.cpp) git history. ([`1fac1073`](https://github.com/termux/termux-exec-package/commit/1fac1073))   - Add support to execute ELF files by passing them to `/system/bin/linker*` to bypass android app data file exec restriction if using `targetSdkVersion` `>= 28` on Android `>= 10`. Check [technical](https://github.com/termux/termux-exec-package/blob/master/site/pages/en/projects/docs/technical/index.md) and [system linker exec](https://github.com/agnostic-apollo/Android-Docs/blob/master/site/pages/en/projects/docs/apps/processes/app-data-file-execute-restrictions.md#system-linker-exec) docs for info on how `system_linker_exec` works and why it is needed. ([`db738a11`](https://github.com/termux/termux-exec-package/commit/db738a11)) - The `libtermux-exec-linker-ld-preload.so` `$LD_PRELOAD` library variant has been added and is meant to intercept additional functions to solve other issues specific to `system_linker_exec` execution. The `libtermux-exec-direct-ld-preload.so` variant needs to support `system_linker_exec` as well as during package updates, before the `linker` variant is set as primary variant, the `direct` variant will get used for certain commands as it gets installed as the primary variant by default, and commands will fail if `system_linker_exec` is required to bypass execution restrictions. The primary `$LD_PRELOAD` library variant to be used is set by copying it to `$TERMUX__PREFIX/usr/lib/libtermux-exec-ld-preload.so` and this path is exported in `$LD_PRELOAD` by `login` script. This is done by the `postinst` script run during package installation, which runs `termux-exec-ld-preload-lib setup` to set the correct variant as per the execution type required for the Termux environment of the host device by running `termux-exec-system-linker-exec is-enabled` to check if `system_linker_exec` is to be enabled. ([`db738a11`](https://github.com/termux/termux-exec-package/commit/db738a11)) -- Since when executing with linker, the `/proc/self/exe` will be set to linker path, export `TERMUX_EXEC__PROC_SELF_EXE` environment variable with actual path to executable being executed so that packages can be patched to read it instead. Additional hocking will need to be done for programs that read `/proc//exe`. ([`db738a11`](https://github.com/termux/termux-exec-package/commit/db738a11)) +- Since when executing with linker, the `/proc/self/exe` will be set to linker path, export `TERMUX_EXEC__PROC_SELF_EXE` environment variable with actual path to executable being executed so that packages can be patched to read it instead. Additional hocking will need to be done for programs that read `/proc//exe`. ([`5ea25ee3`](https://github.com/termux/termux-exec-package/commit/5ea25ee3), [`db738a11`](https://github.com/termux/termux-exec-package/commit/db738a11)) - Added the `string` `TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE` environment variable for whether to use `system_linker_exec` if `TERMUX_EXEC__EXECVE_CALL__INTERCEPT` is enabled. If set to `disable`, `system_linker_exec` will be disabled. If set to `enable`, then `system_linker_exec` will be enabled but only if required. If set to `force`, then `system_linker_exec` will be force enabled even if not required and is supported. The default value is `enable`. Check `isSystemLinkerExecEnabled()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h#L20), [2](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c#L31)) and `shouldEnableSystemLinkerExecForFile()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h#L55), [2](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c#L2137)) in `TermuxExecLDPreload.h` and implemented by `TermuxExecLDPreload.c` for more info and how its handled. The `system_linker_exec` will now engage for executable or interpreter paths that are under `TERMUX_APP__DATA_DIR` or `TERMUX_APP__LEGACY_DATA_DIR` instead of `TERMUX__ROOTFS` (`TERMUX_BASE_DIR`). ([`db738a11`](https://github.com/termux/termux-exec-package/commit/db738a11))   From 8793fc71450efd360f26c1a0be44c0a1e288954e Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Sat, 29 Mar 2025 02:29:14 +0500 Subject: [PATCH 43/47] Fixed: Do not log some debug messages to `stderr` to keep logging output synchronous if `stdout` and `stderr` streams are captured separately as other log entries are being sent to `stdout` The existence of `stderr` being set can also be considered a failure by clients. --- .../ld_preload/termux-exec-ld-preload-lib.in | 1 - .../ld_preload/termux-exec-system-linker-exec.in | 15 +++++++-------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in index d695762..6742a1c 100644 --- a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in +++ b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in @@ -24,7 +24,6 @@ esac termux_exec__ld_preload_lib__log() { local log_level="${1}"; shift; if [ "$TERMUX_EXEC__LD_PRELOAD_LIB__LOG_LEVEL" -ge "$log_level" ]; then echo "@TERMUX__LNAME@-exec:" "$@"; fi } termux_exec__ld_preload_lib__log_error() { echo "@TERMUX__LNAME@-exec:" "$@" 1>&2; } -termux_exec__ld_preload_lib__log_error_for_level() { local log_level="${1}"; shift; if [ "$TERMUX_EXEC__LD_PRELOAD_LIB__LOG_LEVEL" -ge "$log_level" ]; then echo "@TERMUX__LNAME@-exec:" "$@" 1>&2; fi } diff --git a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in index 98eb0b2..4c0d446 100644 --- a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in +++ b/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in @@ -16,7 +16,6 @@ esac termux_exec__system_linker_exec__log() { local log_level="${1}"; shift; if [ "$TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL" -ge "$log_level" ]; then echo "@TERMUX__LNAME@-exec:" "$@"; fi } termux_exec__system_linker_exec__log_error() { echo "@TERMUX__LNAME@-exec:" "$@" 1>&2; } -termux_exec__system_linker_exec__log_error_for_level() { local log_level="${1}"; shift; if [ "$TERMUX_EXEC__SYSTEM_LINKER_EXEC__LOG_LEVEL" -ge "$log_level" ]; then echo "@TERMUX__LNAME@-exec:" "$@" 1>&2; fi } @@ -96,7 +95,7 @@ termux_exec__system_linker_exec__enabled__run_command() { esac ;; esac - termux_exec__system_linker_exec__log_error_for_level 2 "android_build_version_sdk: '$android_build_version_sdk'" + termux_exec__system_linker_exec__log 2 "android_build_version_sdk: '$android_build_version_sdk'" local system_linker_exec_mode="${TERMUX_EXEC__SYSTEM_LINKER_EXEC__MODE:-}" @@ -104,7 +103,7 @@ termux_exec__system_linker_exec__enabled__run_command() { disable|enable|force) :;; *) system_linker_exec_mode="enable";; esac - termux_exec__system_linker_exec__log_error_for_level 2 "system_linker_exec_mode: '$system_linker_exec_mode'" + termux_exec__system_linker_exec__log 2 "system_linker_exec_mode: '$system_linker_exec_mode'" local __system_linker_exec_enabled="false" @@ -117,7 +116,7 @@ termux_exec__system_linker_exec__enabled__run_command() { if [ "$android_build_version_sdk" -ge 29 ]; then system_linker_exec_available="true" fi - termux_exec__system_linker_exec__log_error_for_level 2 "system_linker_exec_available: '$system_linker_exec_available'" + termux_exec__system_linker_exec__log 2 "system_linker_exec_available: '$system_linker_exec_available'" if [ "$system_linker_exec_available" = "true" ]; then __system_linker_exec_enabled="true" @@ -138,10 +137,10 @@ termux_exec__system_linker_exec__enabled__run_command() { se_process_context="$(cat "/proc/self/attr/current")" || true if [ -z "$se_process_context" ]; then termux_exec__system_linker_exec__log_error "Failed to read se_process_context value from '/proc/self/attr/current': '$se_process_context'" - termux_exec__system_linker_exec__log_error_for_level 2 "se_process_context_available: 'false'" + termux_exec__system_linker_exec__log 2 "se_process_context_available: 'false'" app_data_file_exec_exempted="true" else - termux_exec__system_linker_exec__log_error_for_level 2 "se_process_context_from_file: '$se_process_context'" + termux_exec__system_linker_exec__log 2 "se_process_context_from_file: '$se_process_context'" case "$se_process_context" in "u:r:untrusted_app_25:"*|"u:r:untrusted_app_27:"*) app_data_file_exec_exempted="true" @@ -149,14 +148,14 @@ termux_exec__system_linker_exec__enabled__run_command() { esac fi - termux_exec__system_linker_exec__log_error_for_level 2 "app_data_file_exec_exempted: '$app_data_file_exec_exempted'" + termux_exec__system_linker_exec__log 2 "app_data_file_exec_exempted: '$app_data_file_exec_exempted'" if [ "$app_data_file_exec_exempted" = "false" ]; then __system_linker_exec_enabled="true" fi fi fi - termux_exec__system_linker_exec__log_error_for_level 2 "system_linker_exec_enabled: '$__system_linker_exec_enabled'" + termux_exec__system_linker_exec__log 2 "system_linker_exec_enabled: '$__system_linker_exec_enabled'" termux_exec__system_linker_exec__set_variable "$output_variable_name" "$__system_linker_exec_enabled" From f3e73062e0a92a849d14946d1c25e9f685c48d75 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 17 Apr 2025 01:04:34 +0500 Subject: [PATCH 44/47] Changed: Move `lib/termux-exec_nos_c_tre` directory to `lib/termux-exec_nos_c/tre` and `termux/api/termux_exec/ld_preload` sources to `termux/api/termux_exec/service/ld_preload` stage 1 --- .../{ => service}/ld_preload/termux-exec-ld-preload-lib.in | 0 .../{ => service}/ld_preload/termux-exec-system-linker-exec.in | 0 .../ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c | 0 .../ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c | 0 .../termux/termux_exec__nos__c/v1/TermuxExecLibraryConfig.h | 0 .../api/termux_exec/service}/ld_preload/TermuxExecLDPreload.h | 0 .../termux_exec/service}/ld_preload/direct/exec/ExecIntercept.h | 0 .../service}/ld_preload/direct/exec/ExecVariantsIntercept.h | 0 .../v1/termux/os/process/termux_exec/TermuxExecProcess.h | 0 .../command/environment/termux_exec/TermuxExecShellEnvironment.h | 0 .../tre}/src/TermuxExecLibraryConfig.c | 0 .../api/termux_exec/service}/ld_preload/TermuxExecLDPreload.c | 0 .../termux_exec/service}/ld_preload/direct/exec/ExecIntercept.c | 0 .../service}/ld_preload/direct/exec/ExecVariantsIntercept.c | 0 .../tre}/src/termux/os/process/termux_exec/TermuxExecProcess.c | 0 .../command/environment/termux_exec/TermuxExecShellEnvironment.c | 0 .../tre}/tests/libtermux-exec_nos_c_tre_tests.in | 0 .../scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in | 0 .../ld_preload/direct/exec/exec-intercept_runtime-script-tests.in | 0 .../service}/ld_preload/direct/exec/files/print-args-binary.c | 0 .../service}/ld_preload/direct/exec/files/print-args-binary.sym | 0 .../ld_preload/direct/exec/files/print-args-linux-script.sh | 0 .../ld_preload/direct/exec/files/print-args-linux-script.sh.sym | 0 .../ld_preload/direct/exec/files/print-args-termux-script.sh.in | 0 .../ld_preload/direct/exec/files/print-args-termux-script.sh.sym | 0 .../tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c | 0 .../tre}/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c | 0 .../ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c | 0 .../ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c | 0 29 files changed, 0 insertions(+), 0 deletions(-) rename app/main/scripts/termux/api/termux_exec/{ => service}/ld_preload/termux-exec-ld-preload-lib.in (100%) rename app/main/scripts/termux/api/termux_exec/{ => service}/ld_preload/termux-exec-system-linker-exec.in (100%) rename app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/{ => service}/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c (100%) rename app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/{ => service}/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c (100%) rename lib/{termux-exec_nos_c_tre => termux-exec_nos_c/tre}/include/termux/termux_exec__nos__c/v1/TermuxExecLibraryConfig.h (100%) rename lib/{termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec => termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service}/ld_preload/TermuxExecLDPreload.h (100%) rename lib/{termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec => termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service}/ld_preload/direct/exec/ExecIntercept.h (100%) rename lib/{termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec => termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service}/ld_preload/direct/exec/ExecVariantsIntercept.h (100%) rename lib/{termux-exec_nos_c_tre => termux-exec_nos_c/tre}/include/termux/termux_exec__nos__c/v1/termux/os/process/termux_exec/TermuxExecProcess.h (100%) rename lib/{termux-exec_nos_c_tre => termux-exec_nos_c/tre}/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h (100%) rename lib/{termux-exec_nos_c_tre => termux-exec_nos_c/tre}/src/TermuxExecLibraryConfig.c (100%) rename lib/{termux-exec_nos_c_tre/src/termux/api/termux_exec => termux-exec_nos_c/tre/src/termux/api/termux_exec/service}/ld_preload/TermuxExecLDPreload.c (100%) rename lib/{termux-exec_nos_c_tre/src/termux/api/termux_exec => termux-exec_nos_c/tre/src/termux/api/termux_exec/service}/ld_preload/direct/exec/ExecIntercept.c (100%) rename lib/{termux-exec_nos_c_tre/src/termux/api/termux_exec => termux-exec_nos_c/tre/src/termux/api/termux_exec/service}/ld_preload/direct/exec/ExecVariantsIntercept.c (100%) rename lib/{termux-exec_nos_c_tre => termux-exec_nos_c/tre}/src/termux/os/process/termux_exec/TermuxExecProcess.c (100%) rename lib/{termux-exec_nos_c_tre => termux-exec_nos_c/tre}/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c (100%) rename lib/{termux-exec_nos_c_tre => termux-exec_nos_c/tre}/tests/libtermux-exec_nos_c_tre_tests.in (100%) rename lib/{termux-exec_nos_c_tre => termux-exec_nos_c/tre}/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in (100%) rename lib/{termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec => termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service}/ld_preload/direct/exec/exec-intercept_runtime-script-tests.in (100%) rename lib/{termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec => termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service}/ld_preload/direct/exec/files/print-args-binary.c (100%) rename lib/{termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec => termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service}/ld_preload/direct/exec/files/print-args-binary.sym (100%) rename lib/{termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec => termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service}/ld_preload/direct/exec/files/print-args-linux-script.sh (100%) rename lib/{termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec => termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service}/ld_preload/direct/exec/files/print-args-linux-script.sh.sym (100%) rename lib/{termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec => termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service}/ld_preload/direct/exec/files/print-args-termux-script.sh.in (100%) rename lib/{termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec => termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service}/ld_preload/direct/exec/files/print-args-termux-script.sh.sym (100%) rename lib/{termux-exec_nos_c_tre => termux-exec_nos_c/tre}/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c (100%) rename lib/{termux-exec_nos_c_tre => termux-exec_nos_c/tre}/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c (100%) rename lib/{termux-exec_nos_c_tre/tests/src/termux/api/termux_exec => termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service}/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c (100%) rename lib/{termux-exec_nos_c_tre/tests/src/termux/api/termux_exec => termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service}/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c (100%) diff --git a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in b/app/main/scripts/termux/api/termux_exec/service/ld_preload/termux-exec-ld-preload-lib.in similarity index 100% rename from app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in rename to app/main/scripts/termux/api/termux_exec/service/ld_preload/termux-exec-ld-preload-lib.in diff --git a/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in b/app/main/scripts/termux/api/termux_exec/service/ld_preload/termux-exec-system-linker-exec.in similarity index 100% rename from app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in rename to app/main/scripts/termux/api/termux_exec/service/ld_preload/termux-exec-system-linker-exec.in diff --git a/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c b/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/service/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c similarity index 100% rename from app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c rename to app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/service/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c diff --git a/app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c b/app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/service/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c similarity index 100% rename from app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c rename to app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/service/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/TermuxExecLibraryConfig.h b/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/TermuxExecLibraryConfig.h similarity index 100% rename from lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/TermuxExecLibraryConfig.h rename to lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/TermuxExecLibraryConfig.h diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h b/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.h similarity index 100% rename from lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h rename to lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.h diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h b/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept.h similarity index 100% rename from lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.h rename to lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept.h diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.h b/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/direct/exec/ExecVariantsIntercept.h similarity index 100% rename from lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.h rename to lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/direct/exec/ExecVariantsIntercept.h diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/os/process/termux_exec/TermuxExecProcess.h b/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/os/process/termux_exec/TermuxExecProcess.h similarity index 100% rename from lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/os/process/termux_exec/TermuxExecProcess.h rename to lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/os/process/termux_exec/TermuxExecProcess.h diff --git a/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h b/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h similarity index 100% rename from lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h rename to lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.h diff --git a/lib/termux-exec_nos_c_tre/src/TermuxExecLibraryConfig.c b/lib/termux-exec_nos_c/tre/src/TermuxExecLibraryConfig.c similarity index 100% rename from lib/termux-exec_nos_c_tre/src/TermuxExecLibraryConfig.c rename to lib/termux-exec_nos_c/tre/src/TermuxExecLibraryConfig.c diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c b/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.c similarity index 100% rename from lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c rename to lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.c diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c b/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept.c similarity index 100% rename from lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c rename to lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept.c diff --git a/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.c b/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecVariantsIntercept.c similarity index 100% rename from lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.c rename to lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecVariantsIntercept.c diff --git a/lib/termux-exec_nos_c_tre/src/termux/os/process/termux_exec/TermuxExecProcess.c b/lib/termux-exec_nos_c/tre/src/termux/os/process/termux_exec/TermuxExecProcess.c similarity index 100% rename from lib/termux-exec_nos_c_tre/src/termux/os/process/termux_exec/TermuxExecProcess.c rename to lib/termux-exec_nos_c/tre/src/termux/os/process/termux_exec/TermuxExecProcess.c diff --git a/lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c b/lib/termux-exec_nos_c/tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c similarity index 100% rename from lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c rename to lib/termux-exec_nos_c/tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c diff --git a/lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in b/lib/termux-exec_nos_c/tre/tests/libtermux-exec_nos_c_tre_tests.in similarity index 100% rename from lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in rename to lib/termux-exec_nos_c/tre/tests/libtermux-exec_nos_c_tre_tests.in diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in b/lib/termux-exec_nos_c/tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in similarity index 100% rename from lib/termux-exec_nos_c_tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in rename to lib/termux-exec_nos_c/tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/exec-intercept_runtime-script-tests.in b/lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/exec-intercept_runtime-script-tests.in similarity index 100% rename from lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/exec-intercept_runtime-script-tests.in rename to lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/exec-intercept_runtime-script-tests.in diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary.c b/lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-binary.c similarity index 100% rename from lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary.c rename to lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-binary.c diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary.sym b/lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-binary.sym similarity index 100% rename from lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary.sym rename to lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-binary.sym diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh b/lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-linux-script.sh similarity index 100% rename from lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh rename to lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-linux-script.sh diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh.sym b/lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-linux-script.sh.sym similarity index 100% rename from lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh.sym rename to lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-linux-script.sh.sym diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-termux-script.sh.in b/lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-termux-script.sh.in similarity index 100% rename from lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-termux-script.sh.in rename to lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-termux-script.sh.in diff --git a/lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-termux-script.sh.sym b/lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-termux-script.sh.sym similarity index 100% rename from lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-termux-script.sh.sym rename to lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-termux-script.sh.sym diff --git a/lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c b/lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c similarity index 100% rename from lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c rename to lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c diff --git a/lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c b/lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c similarity index 100% rename from lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c rename to lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c diff --git a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c b/lib/termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c similarity index 100% rename from lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c rename to lib/termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c diff --git a/lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c b/lib/termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c similarity index 100% rename from lib/termux-exec_nos_c_tre/tests/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c rename to lib/termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c From 578549c09f8692dcb758c3fa149ab5509fd0987f Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 17 Apr 2025 01:15:39 +0500 Subject: [PATCH 45/47] Changed: Move `lib/termux-exec_nos_c_tre` directory to `lib/termux-exec_nos_c/tre` and `api/termux_exec/ld_preload` sources to `api/termux_exec/service/ld_preload` stage 2 The Termux Runtime Environment `tre` is a variant of the `termux-exec_nos_c` library and so its sources should be under the `tre` sub directory to be consistent with the library naming and directory hierarchy convention. For example, for a `foo_jvm_java` library with the Android App Runtime Environment `aare` and Java Runtime Environment `jre` variants that share common code, the directory hierarchy would be `lib/foo_jvm_java/{share,aare,jre}`, where `share` sub directory contains the common code. The API service code must exist under the `service` directory like `api//service/` as other directories like `config`/`utils` could exist that are shared between different API services, as they would otherwise conflict with the service name directory. --- Makefile | 76 +++++++++---------- app/main/tests/termux-exec-tests.in | 4 +- .../TermuxExecDirectLDPreloadEntryPoint.c | 4 +- .../TermuxExecLinkerLDPreloadEntryPoint.c | 4 +- .../ld_preload/direct/exec/ExecIntercept.h | 10 +-- .../service/ld_preload/TermuxExecLDPreload.c | 2 +- .../ld_preload/direct/exec/ExecIntercept.c | 6 +- .../direct/exec/ExecVariantsIntercept.c | 4 +- .../tests/libtermux-exec_nos_c_tre_tests.in | 10 +-- ...mux-exec_nos_c_tre_runtime-script-tests.in | 4 +- ...rmux-exec_nos_c_tre_runtime-binary-tests.c | 2 +- ...btermux-exec_nos_c_tre_unit-binary-tests.c | 2 +- .../exec/ExecIntercept_RuntimeBinaryTests.c | 4 +- .../exec/ExecIntercept_UnitBinaryTests.c | 2 +- .../pages/en/projects/docs/technical/index.md | 12 +-- site/pages/en/projects/docs/usage/index.md | 12 +-- 16 files changed, 79 insertions(+), 79 deletions(-) diff --git a/Makefile b/Makefile index dbc4495..2f68c88 100644 --- a/Makefile +++ b/Makefile @@ -192,19 +192,19 @@ FSANTIZE_FLAGS += -fsanitize=address -fsanitize-recover=address -fno-omit-frame- override LIBTERMUX_EXEC__NOS__C__SOURCE_FILES := \ - lib/termux-exec_nos_c_tre/src/TermuxExecLibraryConfig.c \ - lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c \ - lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c \ - lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecVariantsIntercept.c \ - lib/termux-exec_nos_c_tre/src/termux/os/process/termux_exec/TermuxExecProcess.c \ - lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c + lib/termux-exec_nos_c/tre/src/TermuxExecLibraryConfig.c \ + lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.c \ + lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept.c \ + lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecVariantsIntercept.c \ + lib/termux-exec_nos_c/tre/src/termux/os/process/termux_exec/TermuxExecProcess.c \ + lib/termux-exec_nos_c/tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c override LIBTERMUX_EXEC__NOS__C__OBJECT_FILES := $(patsubst lib/%.c,$(TMP_BUILD_OUTPUT_DIR)/lib/%.o,$(LIBTERMUX_EXEC__NOS__C__SOURCE_FILES)) override LIBTERMUX_EXEC__NOS__C__CPPFLAGS := \ - $(CPPFLAGS) -I "$(TERMUX__PREFIX)/include/termux-core" -I "lib/termux-exec_nos_c_tre/include" + $(CPPFLAGS) -I "$(TERMUX__PREFIX)/include/termux-core" -I "lib/termux-exec_nos_c/tre/include" -override LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR := $(TESTS_BUILD_OUTPUT_DIR)/lib/termux-exec_nos_c_tre +override LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR := $(TESTS_BUILD_OUTPUT_DIR)/lib/termux-exec_nos_c/tre ifneq ($(LIBTERMUX_EXEC__NOS__C__EXECVE_CALL__CHECK_ARGV0_BUFFER_OVERFLOW),1) @@ -290,7 +290,7 @@ build-libtermux-exec_nos_c_tre: @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre" @mkdir -p $(LIB_BUILD_OUTPUT_DIR) - @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/lib/*.o" + @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c/tre/lib/*.o" for source_file in $(LIBTERMUX_EXEC__NOS__C__SOURCE_FILES); do \ mkdir -p "$$(dirname "$(TMP_BUILD_OUTPUT_DIR)/$$source_file")" || exit $$?; \ $(CC) -c $(CFLAGS) $(LIBTERMUX_EXEC__NOS__C__CPPFLAGS) \ @@ -316,71 +316,71 @@ build-libtermux-exec_nos_c_tre: - @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/tests/*" + @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c/tre/tests/*" @mkdir -p $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR) - @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests" - $(call replace-termux-constants,lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests,$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)) + @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c/tre/tests/libtermux-exec_nos_c_tre_tests" + $(call replace-termux-constants,lib/termux-exec_nos_c/tre/tests/libtermux-exec_nos_c_tre_tests,$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)) chmod 700 $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/libtermux-exec_nos_c_tre_tests - @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/tests/bin/libtermux-exec_nos_c_tre_unit-binary-tests" + @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c/tre/tests/bin/libtermux-exec_nos_c_tre_unit-binary-tests" @mkdir -p $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin - @# `nm --demangle --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/bin/libtermux-exec_nos_c_tre_unit-binary-tests-fsanitize` + @# `nm --demangle --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c/tre/bin/libtermux-exec_nos_c_tre_unit-binary-tests-fsanitize` $(TERMUX_EXEC_EXECUTABLE__C__BUILD_COMMAND) -O0 -g \ $(FSANTIZE_FLAGS) \ -o $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin/libtermux-exec_nos_c_tre_unit-binary-tests-fsanitize \ - lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c \ + lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c \ $(TERMUX_EXEC_EXECUTABLE__C__POST_LDFLAGS) - @# `nm --demangle --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/bin/libtermux-exec_nos_c_tre_unit-binary-tests-nofsanitize` + @# `nm --demangle --defined-only --extern-only /home/builder/.termux-build/termux-exec/src/build/output/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c/tre/bin/libtermux-exec_nos_c_tre_unit-binary-tests-nofsanitize` $(TERMUX_EXEC_EXECUTABLE__C__BUILD_COMMAND) -O0 -g \ -o $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin/libtermux-exec_nos_c_tre_unit-binary-tests-nofsanitize \ - lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c \ + lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c \ $(TERMUX_EXEC_EXECUTABLE__C__POST_LDFLAGS) - @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/tests/bin/libtermux-exec_nos_c_tre_runtime-binary-tests$(TERMUX_EXEC_PKG__TESTS__API_LEVEL)" + @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c/tre/tests/bin/libtermux-exec_nos_c_tre_runtime-binary-tests$(TERMUX_EXEC_PKG__TESTS__API_LEVEL)" @mkdir -p $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin $(TERMUX_EXEC_EXECUTABLE__C__BUILD_COMMAND) -O0 -g \ $(FSANTIZE_FLAGS) \ -o $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin/libtermux-exec_nos_c_tre_runtime-binary-tests-fsanitize$(TERMUX_EXEC_PKG__TESTS__API_LEVEL) \ - lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c \ + lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c \ $(TERMUX_EXEC_EXECUTABLE__C__POST_LDFLAGS) $(TERMUX_EXEC_EXECUTABLE__C__BUILD_COMMAND) -O0 -g \ -o $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin/libtermux-exec_nos_c_tre_runtime-binary-tests-nofsanitize$(TERMUX_EXEC_PKG__TESTS__API_LEVEL) \ - lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c \ + lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c \ $(TERMUX_EXEC_EXECUTABLE__C__POST_LDFLAGS) - @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/tests/scripts/*" + @printf "\ntermux-exec-package: %s\n" "Building lib/termux-exec_nos_c/tre/tests/scripts/*" @mkdir -p $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts - find lib/termux-exec_nos_c_tre/tests/scripts -type f -name '*.c' -print0 | xargs -0 -n1 sh -c \ - 'output_file="$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts/$$(printf "%s" "$$0" | sed -e "s|^lib/termux-exec_nos_c_tre/tests/scripts/||" -e "s/\.c$$//")" && mkdir -p "$$(dirname "$$output_file")" && $(CC) $(CFLAGS) -O0 -fPIE -pie $(LDFLAGS) -g "$$0" -o "$$output_file"' - find lib/termux-exec_nos_c_tre/tests/scripts -type f -name '*.sh' -print0 | xargs -0 -n1 sh -c \ - 'output_file="$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts/$$(printf "%s" "$$0" | sed -e "s|^lib/termux-exec_nos_c_tre/tests/scripts/||")" && mkdir -p "$$(dirname "$$output_file")" && cp -a "$$0" "$$output_file"' - find lib/termux-exec_nos_c_tre/tests/scripts -type f -name "*.in" -print0 | xargs -0 -n1 sh -c \ - 'output_file="$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts/$$(printf "%s" "$$0" | sed -e "s|^lib/termux-exec_nos_c_tre/tests/scripts/||" -e "s/\.in$$//")" && mkdir -p "$$(dirname "$$output_file")" && sed $(TERMUX__CONSTANTS__SED_ARGS) "$$0" > "$$output_file"' - find lib/termux-exec_nos_c_tre/tests/scripts -type l -print0 | xargs -0 -n1 sh -c \ - 'output_file="$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts/$$(printf "%s" "$$0" | sed -e "s|^lib/termux-exec_nos_c_tre/tests/scripts/||")" && mkdir -p "$$(dirname "$$output_file")" && cp -a "$$0" "$$output_file"' + find lib/termux-exec_nos_c/tre/tests/scripts -type f -name '*.c' -print0 | xargs -0 -n1 sh -c \ + 'output_file="$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts/$$(printf "%s" "$$0" | sed -e "s|^lib/termux-exec_nos_c/tre/tests/scripts/||" -e "s/\.c$$//")" && mkdir -p "$$(dirname "$$output_file")" && $(CC) $(CFLAGS) -O0 -fPIE -pie $(LDFLAGS) -g "$$0" -o "$$output_file"' + find lib/termux-exec_nos_c/tre/tests/scripts -type f -name '*.sh' -print0 | xargs -0 -n1 sh -c \ + 'output_file="$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts/$$(printf "%s" "$$0" | sed -e "s|^lib/termux-exec_nos_c/tre/tests/scripts/||")" && mkdir -p "$$(dirname "$$output_file")" && cp -a "$$0" "$$output_file"' + find lib/termux-exec_nos_c/tre/tests/scripts -type f -name "*.in" -print0 | xargs -0 -n1 sh -c \ + 'output_file="$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts/$$(printf "%s" "$$0" | sed -e "s|^lib/termux-exec_nos_c/tre/tests/scripts/||" -e "s/\.in$$//")" && mkdir -p "$$(dirname "$$output_file")" && sed $(TERMUX__CONSTANTS__SED_ARGS) "$$0" > "$$output_file"' + find lib/termux-exec_nos_c/tre/tests/scripts -type l -print0 | xargs -0 -n1 sh -c \ + 'output_file="$(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts/$$(printf "%s" "$$0" | sed -e "s|^lib/termux-exec_nos_c/tre/tests/scripts/||")" && mkdir -p "$$(dirname "$$output_file")" && cp -a "$$0" "$$output_file"' find $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/scripts -type f -exec chmod 700 "{}" \; build-libtermux-exec_nos_c_tre_runtime-binary-tests: - @printf "termux-exec-package: %s\n" "Building lib/termux-exec_nos_c_tre/tests/bin/libtermux-exec_nos_c_tre_runtime-binary-tests$(TERMUX_EXEC_PKG__TESTS__API_LEVEL)" + @printf "termux-exec-package: %s\n" "Building lib/termux-exec_nos_c/tre/tests/bin/libtermux-exec_nos_c_tre_runtime-binary-tests$(TERMUX_EXEC_PKG__TESTS__API_LEVEL)" @mkdir -p $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin $(TERMUX_EXEC_EXECUTABLE__C__BUILD_COMMAND) -O0 -g \ $(FSANTIZE_FLAGS) \ -o $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin/libtermux-exec_nos_c_tre_runtime-binary-tests-fsanitize$(TERMUX_EXEC_PKG__TESTS__API_LEVEL) \ - lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c \ + lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c \ $(TERMUX_EXEC_EXECUTABLE__C__POST_LDFLAGS) $(TERMUX_EXEC_EXECUTABLE__C__BUILD_COMMAND) -O0 -g \ -o $(LIBTERMUX_EXEC__NOS__C__TESTS_BUILD_OUTPUT_DIR)/bin/libtermux-exec_nos_c_tre_runtime-binary-tests-nofsanitize$(TERMUX_EXEC_PKG__TESTS__API_LEVEL) \ - lib/termux-exec_nos_c_tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c \ + lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c \ $(TERMUX_EXEC_EXECUTABLE__C__POST_LDFLAGS) build-libtermux-exec-direct-ld-preload: @@ -398,7 +398,7 @@ build-libtermux-exec-direct-ld-preload: $(TERMUX__CONSTANTS__MACRO_FLAGS) \ -fPIC -shared -fvisibility=hidden \ -o $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec-direct-ld-preload.so \ - app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c \ + app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/service/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c \ -l:libtermux-exec_nos_c_tre.a -l:libtermux-core_nos_c_tre.a @# By default, set `libtermux-exec-direct-ld-preload.so` as the @@ -434,7 +434,7 @@ build-libtermux-exec-linker-ld-preload: $(TERMUX__CONSTANTS__MACRO_FLAGS) \ -fPIC -shared -fvisibility=hidden \ -o $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec-linker-ld-preload.so \ - app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c \ + app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/service/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c \ -l:libtermux-exec_nos_c_tre.a -l:libtermux-core_nos_c_tre.a @@ -455,7 +455,7 @@ install: rm -rf $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec install -d $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec/termux - cp -a lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec/termux/termux_exec__nos__c + cp -a lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/include/termux-exec/termux/termux_exec__nos__c install $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec_nos_c_tre.so $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec_nos_c_tre.so install $(LIB_BUILD_OUTPUT_DIR)/libtermux-exec_nos_c_tre.a $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/lib/libtermux-exec_nos_c_tre.a @@ -533,11 +533,11 @@ test-runtime: all format: - $(CLANG_FORMAT) -i app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c $(LIBTERMUX_EXEC__NOS__C__SOURCE_FILES) + $(CLANG_FORMAT) -i app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/service/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c $(LIBTERMUX_EXEC__NOS__C__SOURCE_FILES) check: - $(CLANG_FORMAT) --dry-run app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c $(LIBTERMUX_EXEC__NOS__C__SOURCE_FILES) + $(CLANG_FORMAT) --dry-run app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/service/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c $(LIBTERMUX_EXEC__NOS__C__SOURCE_FILES) $(CLANG_TIDY) -warnings-as-errors='*' \ - app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c $(LIBTERMUX_EXEC__NOS__C__SOURCE_FILES) -- \ + app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/service/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c $(LIBTERMUX_EXEC__NOS__C__SOURCE_FILES) -- \ $(LIBTERMUX_EXEC__NOS__C__CPPFLAGS) \ $(TERMUX__CONSTANTS__MACRO_FLAGS) diff --git a/app/main/tests/termux-exec-tests.in b/app/main/tests/termux-exec-tests.in index 7b4f345..27e5345 100644 --- a/app/main/tests/termux-exec-tests.in +++ b/app/main/tests/termux-exec-tests.in @@ -241,9 +241,9 @@ termux_exec__libtermux_exec__nos__c__tests__run_command() { termux_exec__tests__log 1 "Running 'libtermux-exec_nos_c_tre_tests'" ( - # shellcheck source=lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in + # shellcheck source=lib/termux-exec_nos_c/tre/tests/libtermux-exec_nos_c_tre_tests.in termux_exec__tests__source_file_from_path \ - "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/libtermux-exec_nos_c_tre_tests" || exit $? + "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c/tre/libtermux-exec_nos_c_tre_tests" || exit $? libtermux_exec__nos__c__tests__main ) diff --git a/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/service/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c b/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/service/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c index 0384d39..22594e1 100644 --- a/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/service/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c +++ b/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/service/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c @@ -3,8 +3,8 @@ #include #include -#include -#include +#include +#include #include /** diff --git a/app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/service/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c b/app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/service/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c index a7fa28b..a8e2faf 100644 --- a/app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/service/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c +++ b/app/termux-exec-linker-ld-preload/src/termux/api/termux_exec/service/ld_preload/linker/TermuxExecLinkerLDPreloadEntryPoint.c @@ -3,8 +3,8 @@ #include #include -#include -#include +#include +#include #include /** diff --git a/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept.h b/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept.h index 78c9d4e..f964af0 100644 --- a/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept.h +++ b/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept.h @@ -85,22 +85,22 @@ extern "C" { * path. * * ``` - * LD_DEBUG=3 /data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary arg1; echo $? + * LD_DEBUG=3 /data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c/tre/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-binary arg1; echo $? * 1 * * # Logcat * linker W [ android linker & debugger ] - * linker D DEBUG: library name "/data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary" too long + * linker D DEBUG: library name "/data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c/tre/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-binary" too long * ``` * * ``` - * (exec -a print-args-binary /data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-binary arg1); echo $? + * (exec -a print-args-binary /data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c/tre/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-binary arg1); echo $? * arg1 * 0 * ``` * * ``` - * (cd /data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files && ./print-args-binary arg1); echo $? + * (cd /data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c/tre/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files && ./print-args-binary arg1); echo $? * arg1 * 0 * ``` @@ -110,7 +110,7 @@ extern "C" { * - https://github.com/termux/termux-app/issues/213 * * See also `TERMUX__PREFIX__BIN_FILE___SAFE_MAX_LEN` in - * https://github.com/termux/termux-core-package/blob/master/lib/termux-core_nos_c_tre/include/termux/termux_core__nos__c/v1/termux/file/TermuxFile.h + * https://github.com/termux/termux-core-package/blob/master/lib/termux-core_nos_c/tre/include/termux/termux_core__nos__c/v1/termux/file/TermuxFile.h * * **See Also:** * - https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits diff --git a/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.c b/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.c index c817e3c..47aaba6 100644 --- a/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.c +++ b/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.c @@ -19,7 +19,7 @@ #include #include -#include +#include #include static const char* LOG_TAG = "ld-preload"; diff --git a/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept.c b/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept.c index f47d552..dccfb75 100644 --- a/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept.c +++ b/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept.c @@ -20,8 +20,8 @@ #include #include -#include -#include +#include +#include #include static const char* LOG_TAG = "exec"; @@ -243,7 +243,7 @@ int execveInterceptInternal(const char *origExecutablePath, char *const argv[], // are either under `/data` or `/mnt`, there is no conflict with // system directories, and so file headers for files under app // data directories should always be read. - // error: "/data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files/print-args-linux-script.sh" \ + // error: "/data/data/com.termux/files/usr/libexec/installed-tests/termux-exec/lib/termux-exec_nos_c/tre/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files/print-args-linux-script.sh" \ // is too small to be an ELF executable: only found 52 bytes // - https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/linker/linker_phdr.cpp;l=216 // error: "/data/data/com.termux/files/usr/bin/login" \ diff --git a/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecVariantsIntercept.c b/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecVariantsIntercept.c index 21abba1..8cc349a 100644 --- a/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecVariantsIntercept.c +++ b/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecVariantsIntercept.c @@ -39,8 +39,8 @@ #include -#include -#include +#include +#include static const char* LOG_TAG = "exec"; diff --git a/lib/termux-exec_nos_c/tre/tests/libtermux-exec_nos_c_tre_tests.in b/lib/termux-exec_nos_c/tre/tests/libtermux-exec_nos_c_tre_tests.in index 29200cf..326edbd 100644 --- a/lib/termux-exec_nos_c/tre/tests/libtermux-exec_nos_c_tre_tests.in +++ b/lib/termux-exec_nos_c/tre/tests/libtermux-exec_nos_c_tre_tests.in @@ -74,7 +74,7 @@ libtermux_exec__nos__c__unit_binary_tests__run_command() { # otherwise command will fail with exit code `1` on Android `< 6` # without any error if `argv[0]` length is `>= 128`. # Check `checkExecArg0BufferOverflow()` function in `ExecIntercept.h`. - cd "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/bin" || exit $? + cd "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c/tre/bin" || exit $? printf -v "$TERMUX_EXEC__TESTS__LOG_LEVEL___N" "%s" "$TERMUX_EXEC__TESTS__LOG_LEVEL" || exit $? export "${TERMUX_EXEC__TESTS__LOG_LEVEL___N?}" || exit $? export LD_PRELOAD="$TERMUX_EXEC__TESTS__PRIMARY_LD_PRELOAD_FILE_PATH" || exit $? @@ -143,7 +143,7 @@ libtermux_exec__nos__c__runtime_binary_tests__run_command() { fi if [ "$ANDROID__BUILD_VERSION_SDK" -ge 28 ] && \ - [ -f "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/bin/libtermux-exec_nos_c_tre_${runtime_binary_tests_variant}28" ]; then + [ -f "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c/tre/bin/libtermux-exec_nos_c_tre_${runtime_binary_tests_variant}28" ]; then runtime_binary_tests_variant+="28" fi @@ -153,7 +153,7 @@ libtermux_exec__nos__c__runtime_binary_tests__run_command() { # otherwise command will fail with exit code `1` on Android `< 6` # without any error if `argv[0]` length is `>= 128`. # Check `checkExecArg0BufferOverflow()` function in `ExecIntercept.h`. - cd "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/bin" || exit $? + cd "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c/tre/bin" || exit $? printf -v "$TERMUX_EXEC__TESTS__LOG_LEVEL___N" "%s" "$TERMUX_EXEC__TESTS__LOG_LEVEL" || exit $? export "${TERMUX_EXEC__TESTS__LOG_LEVEL___N?}" || exit $? ASAN_OPTIONS="fast_unwind_on_malloc=false:detect_leaks=$TERMUX_EXEC__TESTS__DETECT_LEAKS" LSAN_OPTIONS="report_objects=$TERMUX_EXEC__TESTS__DETECT_LEAKS" \ @@ -182,9 +182,9 @@ libtermux_exec__nos__c__runtime_script_tests__run_command() { termux_exec__tests__log 1 "Running 'libtermux-exec_nos_c_tre_runtime-script-tests'" ( - # shellcheck source=lib/termux-exec_nos_c_tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in + # shellcheck source=lib/termux-exec_nos_c/tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in termux_exec__tests__source_file_from_path \ - "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/scripts/libtermux-exec_nos_c_tre_runtime-script-tests" || exit $? + "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c/tre/scripts/libtermux-exec_nos_c_tre_runtime-script-tests" || exit $? libtermux_exec__nos__c__runtime_script_tests__main ) diff --git a/lib/termux-exec_nos_c/tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in b/lib/termux-exec_nos_c/tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in index 2489435..b968be8 100644 --- a/lib/termux-exec_nos_c/tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in +++ b/lib/termux-exec_nos_c/tre/tests/scripts/libtermux-exec_nos_c_tre_runtime-script-tests.in @@ -30,9 +30,9 @@ libtermux_exec__nos__c__runtime_script_tests__run_tests() { ( - # shellcheck source=lib/termux-exec_nos_c_tre/tests/scripts/termux/api/termux_exec/ld_preload/direct/exec/exec-intercept_runtime-script-tests.in + # shellcheck source=lib/termux-exec_nos_c/tre/tests/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/exec-intercept_runtime-script-tests.in termux_exec__tests__source_file_from_path \ - "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/exec-intercept_runtime-script-tests" || exit $? + "$TERMUX_EXEC__TESTS__TESTS_PATH/lib/termux-exec_nos_c/tre/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/exec-intercept_runtime-script-tests" || exit $? ExecIntercept_runTests || exit $? ) || return $? diff --git a/lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c b/lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c index 3e18ce4..d8ae836 100644 --- a/lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c +++ b/lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_runtime-binary-tests.c @@ -48,7 +48,7 @@ static void runTests(); -#include "termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c" +#include "termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c" diff --git a/lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c b/lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c index 7c9823d..c5f7ac5 100644 --- a/lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c +++ b/lib/termux-exec_nos_c/tre/tests/src/libtermux-exec_nos_c_tre_unit-binary-tests.c @@ -35,7 +35,7 @@ static void runTests(); -#include "termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c" +#include "termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c" diff --git a/lib/termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c b/lib/termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c index 368dd89..57ee84d 100644 --- a/lib/termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c +++ b/lib/termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_RuntimeBinaryTests.c @@ -1,7 +1,7 @@ #include #include -#include +#include @@ -488,7 +488,7 @@ void test__execIntercept__Files(const char* termuxExec_tests_testsPath, const ch char* termuxExec__execTestFilesPath = NULL; asprintf_wrapper(&termuxExec__execTestFilesPath, "%s/%s", - termuxExec_tests_testsPath, "lib/termux-exec_nos_c_tre/scripts/termux/api/termux_exec/ld_preload/direct/exec/files"); + termuxExec_tests_testsPath, "lib/termux-exec_nos_c/tre/scripts/termux/api/termux_exec/service/ld_preload/direct/exec/files"); // execlp(), execvp() and execvpe() search for file to be executed in $PATH, diff --git a/lib/termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c b/lib/termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c index 82f5f57..8de28af 100644 --- a/lib/termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c +++ b/lib/termux-exec_nos_c/tre/tests/src/termux/api/termux_exec/service/ld_preload/direct/exec/ExecIntercept_UnitBinaryTests.c @@ -1,4 +1,4 @@ -#include +#include diff --git a/site/pages/en/projects/docs/technical/index.md b/site/pages/en/projects/docs/technical/index.md index e4fb273..764e1d2 100644 --- a/site/pages/en/projects/docs/technical/index.md +++ b/site/pages/en/projects/docs/technical/index.md @@ -9,16 +9,16 @@ page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/docs/@ARK_DOC__VERS The [`termux-exec`](https://github.com/termux/termux-exec-package) package provides utils and libraries for Termux exec. It also provides a shared library that is meant to be preloaded with [`$LD_PRELOAD`](https://man7.org/linux/man-pages/man8/ld.so.8.html) for proper functioning of the Termux execution environment. There are `2` variants of the `$LD_PRELOAD` library provided by `termux-exec-package`. -1. `libtermux-exec-direct-ld-preload.so` with [`TermuxExecDirectLDPreloadEntryPoint.c`](https://github.com/termux/termux-exec-package/blob/v2.0.0/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c) as entry point that is used for the `direct` execution type. -2. `libtermux-exec-linker-ld-preload.so` with [`TermuxExecLinkerLDPreloadEntryPoint.c.c`](https://github.com/termux/termux-exec-package/blob/v2.0.0/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c) as entry point that is used for the [`system_linker_exec`](#system-linker-exec-solution) execution type. +1. `libtermux-exec-direct-ld-preload.so` with [`TermuxExecDirectLDPreloadEntryPoint.c`](https://github.com/termux/termux-exec-package/blob/v2.4.0/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/service/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c) as entry point that is used for the `direct` execution type. +2. `libtermux-exec-linker-ld-preload.so` with [`TermuxExecLinkerLDPreloadEntryPoint.c.c`](https://github.com/termux/termux-exec-package/blob/v2.4.0/app/termux-exec-direct-ld-preload/src/termux/api/termux_exec/service/ld_preload/direct/TermuxExecDirectLDPreloadEntryPoint.c) as entry point that is used for the [`system_linker_exec`](#system-linker-exec-solution) execution type. -The primary `$LD_PRELOAD` library variant to be used is set by copying it to `$TERMUX__PREFIX/usr/lib/libtermux-exec-ld-preload.so` and this path is exported in `$LD_PRELOAD` by [`login`](https://github.com/termux/termux-tools/blob/v1.45.0/scripts/login.in#L42-L54) script. This is done by the [`postinst`](https://github.com/termux/termux-exec-package/blob/v2.0.0/packaging/debian/postinst.in) script run during package installation, which runs [`termux-exec-ld-preload-lib setup`](https://github.com/termux/termux-exec-package/blob/v2.0.0/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-ld-preload-lib.in) to set the correct variant as per the execution type required for the Termux environment of the host device by running [`termux-exec-system-linker-exec is-enabled`](https://github.com/termux/termux-exec-package/blob/v2.0.0/app/main/scripts/termux/api/termux_exec/ld_preload/termux-exec-system-linker-exec.in) to check if `system_linker_exec` is to be enabled. A symlink is not used for `libtermux-exec-ld-preload.so` for potential performance impacts. The `$LD_PRELOAD` variable is currently not exported by the Termux app, like for shell commands started for plugins, check [here](https://github.com/termux/termux-tasker#termux-environment) for more info. +The primary `$LD_PRELOAD` library variant to be used is set by copying it to `$TERMUX__PREFIX/usr/lib/libtermux-exec-ld-preload.so` and this path is exported in `$LD_PRELOAD` by [`login`](https://github.com/termux/termux-tools/blob/v1.45.0/scripts/login.in#L42-L54) script. This is done by the [`postinst`](https://github.com/termux/termux-exec-package/blob/v2.4.0/packaging/debian/postinst.in) script run during package installation, which runs [`termux-exec-ld-preload-lib setup`](https://github.com/termux/termux-exec-package/blob/v2.4.0/app/main/scripts/termux/api/termux_exec/service/ld_preload/termux-exec-ld-preload-lib.in) to set the correct variant as per the execution type required for the Termux environment of the host device by running [`termux-exec-system-linker-exec is-enabled`](https://github.com/termux/termux-exec-package/blob/v2.4.0/app/main/scripts/termux/api/termux_exec/service/ld_preload/termux-exec-system-linker-exec.in) to check if `system_linker_exec` is to be enabled. A symlink is not used for `libtermux-exec-ld-preload.so` for potential performance impacts. The `$LD_PRELOAD` variable is currently not exported by the Termux app, like for shell commands started for plugins, check [here](https://github.com/termux/termux-tasker#termux-environment) for more info. Both variants intercept `exec()` family of functions and support `system_linker_exec` execution, but the `libtermux-exec-linker-ld-preload.so` is meant to intercept additional functions to solve other issues specific to `system_linker_exec` execution, without affecting performance for users using `direct` execution type. The `libtermux-exec-direct-ld-preload.so` variant needs to support `system_linker_exec` as well as during package updates, before the `linker` variant is set as primary variant, the `direct` variant will get used for certain commands as it gets installed as the primary variant by default, and commands will fail if `system_linker_exec` is required to bypass execution restrictions. For backward compatibility, a symlink from `libtermux-exec.so` to `libtermux-exec-ld-preload.so` is also created so that older clients do not break which have exported path to `libtermux-exec.so` in `$LD_PRELOAD` via `login` script of older versions of `termux-tools `package. -The following functions are intercepted by the `$LD_PRELOAD` library variants, which are internally implemented by [`libtermux-exec_nos_c_tre`](https://github.com/termux/termux-exec-package/tree/v2.0.0/lib/termux-exec_nos_c_tre) c library for the Android native operating system (`nos`) running in Termux runtime environment (`tre`). +The following functions are intercepted by the `$LD_PRELOAD` library variants, which are internally implemented by [`libtermux-exec_nos_c_tre`](https://github.com/termux/termux-exec-package/tree/v2.4.0/lib/termux-exec_nos_c/tre) c library for the Android native operating system (`nos`) running in Termux runtime environment (`tre`). - [`exec()`](#exec) @@ -36,7 +36,7 @@ Some older devices/ROM do not support setting `$LD_PRELOAD`. ([1](https://github The `exec()` family of functions are [declared in `unistd.h`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/include/unistd.h;l=92-100) and [implemented by `exec.cpp`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/bionic/exec.cpp) in android [`bionic`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/README.md) `libc` library. The `exec()` functions are wrappers around the [`execve()`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/SYSCALLS.TXT;l=68) system call listed in [`syscalls(2)`](https://man7.org/linux/man-pages/man2/syscalls.2.html) provided by the [android/linux kernel](https://cs.android.com/android/kernel/superproject/+/ebe69964:common/include/linux/syscalls.h;l=790), which can also be directly called with the [`syscall(2)`](https://man7.org/linux/man-pages/man2/syscall.2.html) library function [declared in `unistd.h`](https://cs.android.com/android/platform/superproject/+/android-14.0.0_r1:bionic/libc/include/unistd.h;l=308). Note that there is also a `execve()` wrapper in `unistd.h` around the `execve()` system call. The `termux-exec` overrides the entire `exec()` family of functions, but will not override direct calls to the `execve()` system call via `syscall(2)`, which is usually not directly called by programs. -The Termux `$LD_PRELOAD` library implements the intercepts in [`ExecIntercept.c`](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/exec/ExecIntercept.c) and [`ExecVariantsIntercept.c`](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/exec/ExecVariantsIntercept.c). +The Termux `$LD_PRELOAD` library implements the intercepts in [`ExecIntercept.c`](https://github.com/termux/termux-exec-package/blob/v2.4.0/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/exec/ExecIntercept.c) and [`ExecVariantsIntercept.c`](https://github.com/termux/termux-exec-package/blob/v2.4.0/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/exec/ExecVariantsIntercept.c). **See Also:** @@ -103,7 +103,7 @@ Support in Android `linker` to execute files was added in Android `10`, so this - https://cs.android.com/android/_/android/platform/bionic/+/8f639a40966c630c64166d2657da3ee641303194 - https://cs.android.com/android/_/android/platform/bionic/+/refs/tags/android-10.0.0_r1:linker/linker_main.cpp -The Termux `$LD_PRELOAD` library implements enabling `system_linker_exec` via the `isSystemLinkerExecEnabled()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h#L20), [2](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c#L31)) and `shouldEnableSystemLinkerExecForFile()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h#L55), [2](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c#L2137)) in `TermuxExecLDPreload.h` and implemented by `TermuxExecLDPreload.c`, and called by [`ExecIntercept.c`](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/exec/ExecIntercept.c#L216). +The Termux `$LD_PRELOAD` library implements enabling `system_linker_exec` via the `isSystemLinkerExecEnabled()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.4.0/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.h#L20), [2](https://github.com/termux/termux-exec-package/blob/v2.4.0/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.c#L31)) and `shouldEnableSystemLinkerExecForFile()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.4.0/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.h#L55), [2](https://github.com/termux/termux-exec-package/blob/v2.4.0/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.c#L2137)) in `TermuxExecLDPreload.h` and implemented by `TermuxExecLDPreload.c`, and called by [`ExecIntercept.c`](https://github.com/termux/termux-exec-package/blob/v2.4.0/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/exec/ExecIntercept.c#L216). #### System Linker Exec Issues diff --git a/site/pages/en/projects/docs/usage/index.md b/site/pages/en/projects/docs/usage/index.md index ec9d16b..2a76ec4 100644 --- a/site/pages/en/projects/docs/usage/index.md +++ b/site/pages/en/projects/docs/usage/index.md @@ -61,7 +61,7 @@ The non-legacy [Termux app data directory path](https://github.com/termux/termux If `TERMUX_APP__DATA_DIR` environment variable is not set or value is not valid, then the build value set in [`properties.sh`] file for the app data directory with which `termux-exec` package is compiled with is used, which defaults to `/data/data/@TERMUX_APP__PACKAGE_NAME@`. -The `TERMUX_APP__DATA_DIR___MAX_LEN` is defined in [`TermuxFile.h`](https://github.com/termux/termux-core-package/blob/v0.1.0/lib/termux-core_nos_c_tre/include/termux/termux_core__nos__c/v1/termux/file/TermuxFile.h) and it is the internal buffer length used by `termux-exec` for storing Termux app data directory path and the buffer lengths of all other Termux paths under the app data directory is based on it. The value `69` is the maximum value that will fit the requirement for a valid Android app data directory path, check [termux file path limits](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits) docs for more info. **Packages compiled for Termux must ensure that the `TERMUX_APP__DATA_DIR` value used during compilation is `< TERMUX_APP__DATA_DIR___MAX_LEN`.** +The `TERMUX_APP__DATA_DIR___MAX_LEN` is defined in [`TermuxFile.h`](https://github.com/termux/termux-core-package/blob/v0.4.0/lib/termux-core_nos_c/tre/include/termux/termux_core__nos__c/v1/termux/file/TermuxFile.h) and it is the internal buffer length used by `termux-exec` for storing Termux app data directory path and the buffer lengths of all other Termux paths under the app data directory is based on it. The value `69` is the maximum value that will fit the requirement for a valid Android app data directory path, check [termux file path limits](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits) docs for more info. **Packages compiled for Termux must ensure that the `TERMUX_APP__DATA_DIR` value used during compilation is `< TERMUX_APP__DATA_DIR___MAX_LEN`.** ##   @@ -87,7 +87,7 @@ The legacy [Termux app data directory path](https://github.com/termux/termux-pac If `TERMUX_APP__LEGACY_DATA_DIR` environment variable is not set or value is not valid, then the build value set in [`properties.sh`] file for the app data directory with which `termux-exec` package is compiled with is used, which defaults to `/data/data/@TERMUX_APP__PACKAGE_NAME@`. If the build value is a non-legacy path (`/data/user//` or `/mnt/expand//user/0/`), then it is automatically converted to a legacy path. -The `TERMUX_APP__DATA_DIR___MAX_LEN` is defined in [`TermuxFile.h`](https://github.com/termux/termux-core-package/blob/v0.1.0/lib/termux-core_nos_c_tre/include/termux/termux_core__nos__c/v1/termux/file/TermuxFile.h) and it is the internal buffer length used by `termux-exec` for storing Termux app data directory path and the buffer lengths of all other Termux paths under the app data directory is based on it. The value `69` is the maximum value that will fit the requirement for a valid Android app data directory path, check [termux file path limits](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits) docs for more info. **Packages compiled for Termux must ensure that the `TERMUX_APP__DATA_DIR` value used during compilation is `< TERMUX_APP__DATA_DIR___MAX_LEN`.** +The `TERMUX_APP__DATA_DIR___MAX_LEN` is defined in [`TermuxFile.h`](https://github.com/termux/termux-core-package/blob/v0.4.0/lib/termux-core_nos_c/tre/include/termux/termux_core__nos__c/v1/termux/file/TermuxFile.h) and it is the internal buffer length used by `termux-exec` for storing Termux app data directory path and the buffer lengths of all other Termux paths under the app data directory is based on it. The value `69` is the maximum value that will fit the requirement for a valid Android app data directory path, check [termux file path limits](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits) docs for more info. **Packages compiled for Termux must ensure that the `TERMUX_APP__DATA_DIR` value used during compilation is `< TERMUX_APP__DATA_DIR___MAX_LEN`.** ##   @@ -113,7 +113,7 @@ The [Termux prefix directory path](https://github.com/termux/termux-packages/wik If `TERMUX__PREFIX` environment variable is not set or value is not valid, then the build value set in [`properties.sh`] file for the app data directory with which `termux-exec` package is compiled with is used, which defaults to `/data/data/@TERMUX_APP__PACKAGE_NAME@/files/usr`. If the build value is not accessible `termux-exec` intercepts will return with an error and `errno` should be set that is set by the [`access()`](https://man7.org/linux/man-pages/man2/access.2.html) call. -The `TERMUX__PREFIX_DIR___MAX_LEN` is defined in [`TermuxFile.h`](https://github.com/termux/termux-core-package/blob/v0.1.0/lib/termux-core_nos_c_tre/include/termux/termux_core__nos__c/v1/termux/file/TermuxFile.h) and it is the internal buffer length used by `termux-exec` for storing Termux prefix directory path. The value `90` is the maximum value that will fit the requirement for a valid Termux prefix directory path, check [termux file path limits](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits) docs for more info. **Packages compiled for Termux must ensure that the `TERMUX__PREFIX` value used during compilation is `< TERMUX__PREFIX_DIR___MAX_LEN`.** +The `TERMUX__PREFIX_DIR___MAX_LEN` is defined in [`TermuxFile.h`](https://github.com/termux/termux-core-package/blob/v0.4.0/lib/termux-core_nos_c/tre/include/termux/termux_core__nos__c/v1/termux/file/TermuxFile.h) and it is the internal buffer length used by `termux-exec` for storing Termux prefix directory path. The value `90` is the maximum value that will fit the requirement for a valid Termux prefix directory path, check [termux file path limits](https://github.com/termux/termux-packages/wiki/Termux-file-system-layout#file-path-limits) docs for more info. **Packages compiled for Termux must ensure that the `TERMUX__PREFIX` value used during compilation is `< TERMUX__PREFIX_DIR___MAX_LEN`.** ##   @@ -142,7 +142,7 @@ This is used while deciding whether to use `system_linker_exec` if [`TERMUX_EXEC If `TERMUX__SE_PROCESS_CONTEXT` is not set or value is not valid, then the process context is read from the `/proc/self/attr/current` file. -The `REGEX__PROCESS_CONTEXT` is defined in [`SelinuxUtils.h`](https://github.com/termux/termux-core-package/blob/v0.1.0/lib/termux-core_nos_c_tre/include/termux/termux_core__nos__c/v1/unix/os/selinux/SelinuxUtils.h), check its field docs for more info on the format of the an Android SeLinux process context. +The `REGEX__PROCESS_CONTEXT` is defined in [`SelinuxUtils.h`](https://github.com/termux/termux-core-package/blob/v0.4.0/lib/termux-core_nos_c/tre/include/termux/termux_core__nos__c/v1/unix/os/selinux/SelinuxUtils.h), check its field docs for more info on the format of the an Android SeLinux process context. **See Also:** @@ -237,14 +237,14 @@ Whether to use [System Linker Exec Solution](../technical.md#system-linker-exec- - `force` - The `system_linker_exec` will be force enabled even if not required and is supported. -This is implemented by `isSystemLinkerExecEnabled()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h#L20), [2](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c#L31)) and `shouldEnableSystemLinkerExecForFile()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.h#L55), [2](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/TermuxExecLDPreload.c#L2137)) in `TermuxExecLDPreload.h` and implemented by `TermuxExecLDPreload.c`, and called by [`ExecIntercept.c`](https://github.com/termux/termux-exec-package/blob/v2.0.0/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/exec/ExecIntercept.c#L216). +This is implemented by `isSystemLinkerExecEnabled()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.4.0/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.h#L20), [2](https://github.com/termux/termux-exec-package/blob/v2.4.0/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.c#L31)) and `shouldEnableSystemLinkerExecForFile()` function ([1](https://github.com/termux/termux-exec-package/blob/v2.4.0/lib/termux-exec_nos_c/tre/include/termux/termux_exec__nos__c/v1/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.h#L55), [2](https://github.com/termux/termux-exec-package/blob/v2.4.0/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/service/ld_preload/TermuxExecLDPreload.c#L2137)) in `TermuxExecLDPreload.h` and implemented by `TermuxExecLDPreload.c`, and called by [`ExecIntercept.c`](https://github.com/termux/termux-exec-package/blob/v2.4.0/lib/termux-exec_nos_c/tre/src/termux/api/termux_exec/exec/ExecIntercept.c#L216). If `disable` is set, then `system_linker_exec` will never be used and the default `direct` execution type will be used. If `enable` is set, then `system_linker_exec` will only be used if: - `system_linker_exec` is required to bypass [App Data File Execute Restrictions](../technical.md#app-data-file-execute-restrictions), i.e device is running on Android `>= 10`. - Effective user does not equal root (`0`) and shell (`2000`) user (used for [`adb`](https://developer.android.com/tools/adb)). -- [`TERMUX__SE_PROCESS_CONTEXT`](#TERMUX__SE_PROCESS_CONTEXT) does not start with `PROCESS_CONTEXT_PREFIX__UNTRUSTED_APP_25` (`u:r:untrusted_app_25:`) and `PROCESS_CONTEXT_PREFIX__UNTRUSTED_APP_27` (`u:r:untrusted_app_27:`) for which restrictions are exempted. For more info on them, check [`SelinuxUtils.h`](https://github.com/termux/termux-core-package/blob/v0.1.0/lib/termux-core_nos_c_tre/include/termux/termux_core__nos__c/v1/unix/os/selinux/SelinuxUtils.h). +- [`TERMUX__SE_PROCESS_CONTEXT`](#TERMUX__SE_PROCESS_CONTEXT) does not start with `PROCESS_CONTEXT_PREFIX__UNTRUSTED_APP_25` (`u:r:untrusted_app_25:`) and `PROCESS_CONTEXT_PREFIX__UNTRUSTED_APP_27` (`u:r:untrusted_app_27:`) for which restrictions are exempted. For more info on them, check [`SelinuxUtils.h`](https://github.com/termux/termux-core-package/blob/v0.4.0/lib/termux-core_nos_c/tre/include/termux/termux_core__nos__c/v1/unix/os/selinux/SelinuxUtils.h). - Executable or interpreter path is under [`TERMUX_APP__DATA_DIR`] or [`TERMUX_APP__LEGACY_DATA_DIR`] directory. If `force` is set, then `system_linker_exec` will only be used if: From fc36b43407f99901fdf82ae1b6d2f9804d965517 Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Tue, 6 May 2025 00:31:36 +0500 Subject: [PATCH 46/47] Release: 2.4.0 --- Makefile | 2 +- site/pages/en/projects/releases/2/v2.4.0.md | 31 +++++++++++++++++++++ site/pages/en/projects/releases/index.md | 3 +- 3 files changed, 34 insertions(+), 2 deletions(-) create mode 100644 site/pages/en/projects/releases/2/v2.4.0.md diff --git a/Makefile b/Makefile index 2f68c88..e4805c2 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -export TERMUX_EXEC_PKG__VERSION := 2.3.0 +export TERMUX_EXEC_PKG__VERSION := 2.4.0 export TERMUX_EXEC_PKG__ARCH export TERMUX_EXEC_PKG__INSTALL_PREFIX export TERMUX_EXEC_PKG__TESTS__API_LEVEL := diff --git a/site/pages/en/projects/releases/2/v2.4.0.md b/site/pages/en/projects/releases/2/v2.4.0.md new file mode 100644 index 0000000..65ee849 --- /dev/null +++ b/site/pages/en/projects/releases/2/v2.4.0.md @@ -0,0 +1,31 @@ +--- +page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/2/v2.4.0.html" +--- + +# termux-exec-package v2.4.0 - 2025-05-05 + +## Changelog + +**Commit history:** [`v2.3.0...v2.4.0`](https://github.com/termux/termux-exec-package/compare/v2.3.0...v2.4.0) + +  + + + +### Changed + +- Move `lib/termux-exec_nos_c_tre` directory to `lib/termux-exec_nos_c/tre` and `api/termux_exec/ld_preload` sources to `api/termux_exec/service/ld_preload`. ([`f3e73062`](https://github.com/termux/termux-exec-package/commit/f3e73062), [`578549c0`](https://github.com/termux/termux-exec-package/commit/578549c0)) + +##   + +  + + + +### Fixed + +- Do not log some debug messages to `stderr` to keep logging output synchronous if `stdout` and `stderr` streams are captured separately as other log entries are being sent to `stdout`. ([`8793fc71`](https://github.com/termux/termux-exec-package/commit/8793fc71)) + +--- + +  diff --git a/site/pages/en/projects/releases/index.md b/site/pages/en/projects/releases/index.md index d8cb919..56540e2 100644 --- a/site/pages/en/projects/releases/index.md +++ b/site/pages/en/projects/releases/index.md @@ -6,7 +6,7 @@ page_ref: "@ARK_PROJECT__VARIANT@/termux/termux-exec-package/releases/index.html This page lists the releases info for [`termux-exec-package`](https://github.com/termux/termux-exec-package). -**The currently latest release of `termux-exec` is [`v2.3.0`](2/v2.3.0.md).** +**The currently latest release of `termux-exec` is [`v2.4.0`](2/v2.4.0.md).** --- @@ -56,6 +56,7 @@ Open a release to view its release notes and/or changelogs. Changelogs are gener - [`v2.1.0`](2/v2.1.0.md) - [`v2.2.0`](2/v2.2.0.md) - [`v2.3.0`](2/v2.3.0.md) +- [`v2.4.0`](2/v2.4.0.md) --- From 4606100e0b4a33bf4a6cd4579e6cbd0be8cf02ee Mon Sep 17 00:00:00 2001 From: agnostic-apollo Date: Thu, 26 Jun 2025 04:39:45 +0500 Subject: [PATCH 47/47] Added: Add `SECURITY.md` --- SECURITY.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..479f15c --- /dev/null +++ b/SECURITY.md @@ -0,0 +1 @@ +Check https://termux.dev/security for info on Termux security policies and how to report vulnerabilities.