/termux-exec

A execve() wrapper to fix problem with shebangs.

Primary LanguageCApache License 2.0Apache-2.0

termux-exec

A execve(3) (and exec family of functions) wrapper to fix two problems with exec-ing files in Termux.

Problem 1: Cannot execute files not part of the APK

Android 10 started blocking executing files under the app data directory, as that is a W^X violation - files should be either writeable or executable, but not both. Resources:

While there is merit in that general principle, this prevents using Termux and Android as a general computing device, where it should be possible for users to create executable scripts and binaries.

Solution

Create an exec interceptor using LD_PRELOAD, that instead of executing an ELF file directly, executes /system/bin/linker64 /path/to/elf. Explanation follows below.

On Linux, the kernel is normally responsible for loading both the executable and the dynamic linker. The executable is invoked by file path with the execve system call. The kernel loads the executable into the process, and looks for a PT_INTERP entry in its ELF program header table of the file - this specifies the path to the dynamic linker (/system/bin/linker64 for 64-bit Android).

There is another way to load the two ELF objects: since 2018 the dynamic linker can be invoked directly with exec. If passed the filename of an executable, the dynamic linker will load and run the executable itself. So, instead of executing path/to/mybinary, it's possible to execute /system/bin/linker64 /absolute/path/to/mybinary (the linker needs an absolute path).

This is what termux-exec does to circumvent the block on executing files in the data directory - the kernel sees only /system/bin/linker64 being executed.

This also means that we need to extract shebangs. So for example, a call to execute:

./path/to/myscript.sh <args>

where the script has a #!/path/to/interpreter shebang, is replaced with:

/system/bin/linker64 /path/to/interpreter ./path/to/myscript.sh <args>

Implications:

  • It's important that LD_PRELOAD is kept - see e.g. this change in sshd. We could also consider patching this exec interception into the build process of termux packages, so LD_PRELOAD would not be necessary for packages built by the termux-packages repository.

  • The executable will be /system/bin/linker64. So some programs that inspects the executable name (on itself or other programs) using /proc/${PID}/exec or /proc/${PID}/comm (where $(PID} could be self, for the current process) needs to be changed to instead inspect argv0 of the process instead. See this llvm driver change and this pgrep/pkill change.

  • Statically linked binaries will not work. These are rare in Android and Termux, but zig currently produces statically linked binaries against musl libc.

  • The interception using LD_PRELOAD will only work for programs using the C library wrappers for executing a new process, not when using the execve system call directly. Luckily most programs do use this. Programs using raw system calls needs to be patched or be run under proot.

NOTE: The above example used /system/bin/linker64 - on 32-bit systems, the corresponding path is /system/bin/linker.

Problem 2: Shebang paths

A lot of Linux software is written with the assumption that /bin/sh, /usr/bin/env and similar file exists. This is not the case on Android where neither /bin/ nor /usr/ exists.

When building packages for Termux those hard-coded assumptions are patched away - but this does not help with installing scripts and programs from other sources than Termux packages.

Solution

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.

Usage in Termux

This termux-exec package comes preinstalled as an essential package in Termux. Each time a process is spawned by the app, the LD_PRELOAD=$PREFIX/lib/libtermux-exec.so environment variable is setup by the Android app. This environment variable needs to be kept at all times when executing files inside Termux.

In the future the linker --wrap functionaliy might be used to avoid having to rely on LD_PRELOAD always being present.