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..a972f4d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,3 @@ -*.so -*.o -*-actual +/build/install/* +/build/output/* +*.deb diff --git a/LICENSE b/LICENSE index 8dada3e..0f3d0ac 100644 --- a/LICENSE +++ b/LICENSE @@ -1,201 +1,14 @@ - 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. +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__Aache-2.0.md) - "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: + site/* +License: [MIT](licenses/termux__termux-exec-package__MIT.md) diff --git a/Makefile b/Makefile index fa5a3a0..7314ae1 100644 --- a/Makefile +++ b/Makefile @@ -1,20 +1,540 @@ -TERMUX_PREFIX := /data/data/com.termux/files/usr -TERMUX_BASE_DIR := /data/data/com.termux/files -CFLAGS += -Wall -Wextra -Werror -Oz +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 := -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 +export TERMUX__NAME := Termux# Default value: `Termux` +export TERMUX__LNAME := termux# Default value: `termux` -install: libtermux-exec.so - install libtermux-exec.so $(DESTDIR)$(PREFIX)/lib/libtermux-exec.so +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 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` + +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 + + +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 + + + +# 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 + + + +# - 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-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 \ + '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" + @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)/ \; + + + @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) + + @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) \ + -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 $$?; \ + 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) + + + + @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) + + @# 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. + @# 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 + @# `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 + +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 -uninstall: - rm -f $(DESTDIR)$(PREFIX)/lib/libtermux-exec.so -test: libtermux-exec.so - @LD_PRELOAD=${CURDIR}/libtermux-exec.so ./run-tests.sh clean: - rm -f libtermux-exec.so tests/*-actual + 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)/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 + + 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 $(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 + + 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 {} \; + + + 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: + @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 + + + 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 + + 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 + + + rm -rf $(TERMUX_EXEC_PKG__INSTALL_PREFIX)/libexec/installed-tests/termux-exec + + @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 + + + +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: + $(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) + + -.PHONY: clean install test uninstall +.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/README.md b/README.md index ae7caa9..9babae0 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,23 @@ -# termux-exec -A `execve()` wrapper to fix problem with shebangs when running in Termux. - -# Problem -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 -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. +# 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 + +- [Project](#project) + +--- + +  + + + + + +## Project + +**Check the `termux-exec-package` project info [here](site/pages/en/projects/index.md), including `docs` and `releases` info.** + +--- + +  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/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/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/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/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/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 new file mode 100644 index 0000000..38a989b --- /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,367 @@ +#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 + * + * + * + * 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 + */ + +/** + * 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 `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()`. + * + * 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. + * @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, bool shouldEnableSystemLinkerExec, struct TermuxFileHeaderInfo *info); + + + +/** + * 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 + +#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..8789f9d --- /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,194 @@ +#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; + + + + + +/** + * 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 "=" + + + + + +/* + * 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. + * + * @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(); + + + + + +/** + * 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(); + + + + + +/** + * 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 + +#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/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 new file mode 100644 index 0000000..0b695e5 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/src/termux/api/termux_exec/ld_preload/direct/exec/ExecIntercept.c @@ -0,0 +1,706 @@ +#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 = "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; + } + + + + + // 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); + + if (unsetLdVarsFromEnv && areVarsInEnv(envp, LD_VARS_TO_UNSET, LD_VARS_TO_UNSET_SIZE)) { + 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); + + + + 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 = shouldEnableSystemLinkerExec || interpreterSet; + logErrorVVerbose(LOG_TAG, "modify_args: '%d'", modifyArgs); + + const char **newArgv = NULL; + if (modifyArgs) { + if (modifyExecArgs(argv, &newArgv, origExecutablePath, executablePath, + interpreterSet, shouldEnableSystemLinkerExec, &info) != 0 || + newArgv == NULL) { + logErrorDebug(LOG_TAG, "Failed to create modified exec args"); + free(envTermuxProcSelfExe); + free(newEnvp); + return -1; + } + + // Replace executable path if wrapping with linker. + if (shouldEnableSystemLinkerExec) { + executablePath = SYSTEM_LINKER_PATH; + } + + argv = (char **) newArgv; + } + + + + #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); + 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; + 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, bool shouldEnableSystemLinkerExec, 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 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) { + 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; +} + + + +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/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..04a9543 --- /dev/null +++ b/lib/termux-exec_nos_c_tre/src/termux/shell/command/environment/termux_exec/TermuxExecShellEnvironment.c @@ -0,0 +1,53 @@ +#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; +} + +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; +} + + + +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..29200cf --- /dev/null +++ b/lib/termux-exec_nos_c_tre/tests/libtermux-exec_nos_c_tre_tests.in @@ -0,0 +1,202 @@ +#!@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="$( + # 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" \ + "./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}'" + ( + # 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" \ + "./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..3cccaa4 --- /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,505 @@ +#include +#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, expectedReturnValueP, expectedErrnoP, \ + expectedExitCode, expectedOutputRegex, expectedExitCodeP, expectedOutputRegexP, 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, expectedReturnValueP, expectedErrnoP, \ + expectedExitCodeP, expectedOutputRegexP, 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, 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 (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, -1, EISDIR, + 0, NULL, 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; + + 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", + 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", + expectedReturnValue, expectedErrno, expectedReturnValueP, expectedErrnoP, + expectedExitCode, expectedOutputRegex, expectedExitCodeP, expectedOutputRegexP, 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", + 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", + expectedReturnValue, expectedErrno, expectedReturnValueP, expectedErrnoP, + expectedExitCode, expectedOutputRegex, expectedExitCodeP, expectedOutputRegexP, 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, 0, + 0, termuxPackageManagerVersionRegex, 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/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. 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/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 "$@" 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/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/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/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 new file mode 100644 index 0000000..923a67e --- /dev/null +++ b/site/pages/en/projects/releases/index.md @@ -0,0 +1,59 @@ +--- +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) + +### `v2` + +- [`v2.0.0`](2/v2.0.0.md) + +--- + +  diff --git a/termux-exec.c b/termux-exec.c deleted file mode 100644 index 1f8037d..0000000 --- a/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/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