Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Avoid executing ELF files directly #24

Open
wants to merge 7 commits into
base: master
Choose a base branch
from

Conversation

fornwall
Copy link
Member

@fornwall fornwall commented Oct 2, 2023

This is change to work around the Android 10 R^W violation (execution of binary files are not allowed).

See the README for more information.

Helping with testing

Help to test this would be great! For context and more information, see Problem 1 and Solution 1 in the updated README.

NOTE: May totally break your system and need a reinstallation (or require a manual repair in a failsafe session - see below).

Steps (on aarch64 - let me know if you want to test on some other arch):

  • Be sure you have updated packages:
    • pkg up
  • Take a backup (if this is not a installation that you can reinstall termux quickly):
    • cp $PREFIX/lib/libtermux-exec.so $PREFIX/lib/libtermux-exec.so.bak
  • Download and install a test build of termux-exec containing this code:
    • curl -o ~/te-997.deb https://fornwall.me/te-997.deb && apt install --reinstall ~/te-997.deb

After this, new terminal sessions will be using the updated termux-exec version. A way to check this is by running ls -l /proc/self/exe - it should show a symlink pointing to a path ending in bin/linker64, since the linker is what is being executed.

Try things out (run scripts, compile programs, etc) and report if something does not work as a comment in this issue!

This should only affect behaviour on Android 10+ devices, but if you have an old Android version you are welcome to test that as well - that nothing has changed there.

Helping with testing: Debugging tools

  • export TERMUX_EXEC_DEBUG=1 to get verbose logging to stderr
    • This log output may interfere with programs
  • export TERMUX_EXEC_OPTOUT=1 to opt out from termux-exec - it will not do any modification to execve calls

Helping with testing: Known issues

  • Statically linked executables such as zig does not work (linker errors with has unexpected e_type: 2)

Helping with testing: To restore

If things are not completely broken, apt install --reinstall termux-exec=1:1.0 will install the stable version of termux-exec.

If the installation is completely broken, so you can't even start a shell:

  • Start a failsafe session by dragging out the drawer from the left, long press on the NEW SESSION button, and click FAILSAFE in the dialog that shows up.
  • cd $PREFIX/lib && cp libtermux-exec.so.bak libtermux-exec.so
  • You can now start a normal shell session again
    • Restore the termux-exec package properly with: apt install --reinstall termux-exec=1:1.0

Updates

  • 2023-10-17 (file te-997.deb, apt show termux-exec should show version 9:9.7):
    • Various fixes to edge cases and logging
    • Introduce TERMUX_SELF_EXE, the absolute path to the executed file, which can be used as a /proc/self/exec replacement.
  • 2023-10-05
    • Various fixes to edge cases and logging

fornwall added a commit to termux-play-store/termux-packages that referenced this pull request Oct 2, 2023
LLVM looks at /proc/self/exe to determine which binary has been called.
This does not work with the proposed changes for termux-exec:
termux/termux-exec#24

Luckily the fallback to look at argv0 works instead.
fornwall added a commit to termux-play-store/termux-packages that referenced this pull request Oct 2, 2023
LLVM looks at /proc/self/exe to determine which binary has been called.
This does not work with the proposed changes for termux-exec:
termux/termux-exec#24

Luckily the fallback to look at argv0 works instead.
fornwall added a commit to termux-play-store/termux-packages that referenced this pull request Oct 2, 2023
Without -f/--full, pgrep&pkill matches searches by default only on
the binary name, which will not work with the changes proposed in
termux/termux-exec#24, as there the binary
will always be /system/bin/linker64.
fornwall added a commit to termux-play-store/termux-packages that referenced this pull request Oct 2, 2023
Without -f/--full, pgrep&pkill matches searches by default only on
the binary name, which will not work with the changes proposed in
termux/termux-exec#24, as there the binary
will always be /system/bin/linker64.
@fornwall fornwall force-pushed the master branch 4 times, most recently from 2a852ec to 6baed3d Compare October 2, 2023 14:22
@agnostic-apollo
Copy link
Member

Very interesting solution. But yeah, it likely won't be acceptable by play store policies, but could be tried. Although not sure if android could add further selinux restrictions to prevent this.

Btw are you sure in testing, you uninstalled all termux apps, rebooted, and reinstalled, since otherwise untrusted_app_27 selinux process would still be assigned to the app, you can confirm with Termux in-app settings -> About, just making sure.

The executable being /system/bin/linker64 issues may be significant though.

termux-exec.c Outdated

#ifndef TERMUX_BASE_DIR
# define TERMUX_BASE_DIR "/data/data/com.termux/files"
#define TERMUX_BASE_DIR "/data/data/com.termux/files"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do not use hardcoded com.termux values here and elsewhere, it affects termux forks. To get prefix, first check TERMUX__PREFIX env variable, then PREFIX, otherwise default to package build time path @TERMUX_BASE_DIR@. In future, multiple rootfs support may also be added and $PREFIX has been deprecated in favour of TERMUX__PREFIX (note double dash as scope), as it conflicts with other packages like npm, etc. Haven't pushed change to termux-app yet.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks - I'll update!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to look at TERMUX__PREFIX now, with a TERMUX_BASE_DIR value given at compilation time as fallback.

I think PREFIX is too dangerous to use, as you say it can totally break when reused for other purposes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. But do not use literal com.termux value. The whole path @TERMUX_BASE_DIR@ should be replaced during build time in the source by value set in properties.sh. Possibly tests should use this too.

https://github.com/termux/termux-packages/blob/26b05538619ffd93cfd52f252f34d1c025d51be7/scripts/properties.sh#L34

For inspiration, check termux-tools design by grimler.

https://github.com/termux/termux-tools/blob/master/configure.ac

https://github.com/termux/termux-tools/blob/master/Makefile.am

Also do not limit prefix length to 48, package names can be much longer. For example /data/user/10/net.dinglisch.android.taskerm/files will fail with length 49. Even /data/data paths can be longer. Package name alone on playstore can be of length 50. Probably just check if it starts with / since technically packages can be compiled for any prefix, even /data/local/tmp for adb or /mnt paths for external sd.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, good idea not to use $PREFIX, will have to push TERMUX__PREFIX export to termux-app as well.

Copy link
Member Author

@fornwall fornwall Oct 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But do not use literal com.termux value

With the current version (I made changes and squashed commits): We start by using the TERMUX__PREFIX env variable now. If that does not exist, we fall back to TERMUX_BASE_DIR.

TERMUX_BASE_DIR is given at compilation time, as in make TERMUX_BASE_DIR=/a/path/to/package/prefix (the
It's not really hardcoded any longer, but supplied at compile time (the existing package already supplies that at build time: https://github.com/termux/termux-packages/blob/master/packages/termux-exec/build.sh#L10).

Remaining usage of com.termux are ok I think:

  • In Makefile, as a default value if make is executed without any argument
  • In termux-exec.c, but only inside #ifdef UNIT_TEST, so only used for testing. We also test with other prefixes as well in the tests.
  • In test-program.c, which is just a utility test program that can be run manually, not part of the package

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your thorough review and feedback @agnostic-apollo !

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will check code in detail later, outside, but if app is installed on adoptable external sd, then its app data parent directory prefix will be /mnt/expand/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/user/0, which alone has length 55, so 100 is not going to be enough, especially if multiple sub dirs under package name.

You are very welcome. :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so 100 is not going to be enough

Thanks again! So what about 200, that should be enough for everyone perhaps :)? That allows a path such as /mnt/expand/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/user/0/yyyyyyyyyy-yyyyyyyyyy/yyyyyyyyyy-yyyyyyyyyyyyyyyyyyyy/yyyyyyyyyyyyyyyyyyyy/yyyyyyyyyyyyyyyyyyyy-yyyyyy yyyy/xxxxx-xxxxx/yyyyy-yyyyy/zzzzzz-zzzzz/.

The reason I don't want an arbitrary big length is that it would be good to have a reasonable tight limit - this code is going to be injected in a lot of places, so not taking up more stack or heap space than necessary would be great, to decrease risks of interfering with the surrounding problem.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lolz technically the only valid length is PATH_MAX(4096 bytes), but that would be for the full path, not just the prefix. That is also what libraries including mine use as the standard for passing to sys calls. You could reduce it if necessary, but couple of hundred bytes won't matter, but at least log a warning if value is ignored.

termux-exec.c Outdated Show resolved Hide resolved
@sylirre
Copy link
Member

sylirre commented Oct 2, 2023

But yeah, it likely won't be acceptable by play store policies, but could be tried.

As Play Store accepts apps that run binaries by proot and have target SDK 29+, I doubt that would be an issue. Maybe they consider the whole purpose of app (e.g. terminal emulators run binaries and need exec but games do not), who knows...

@ETERNALBLUEbullrun

This comment was marked as off-topic.

@agnostic-apollo
Copy link
Member

I think it may be more likely that they don't know proot is being used, otherwise if someone took interest, proot and this clearly does violate the policy.

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#zippy=%2Cexamples-of-common-violations

@fornwall
Copy link
Member Author

fornwall commented Oct 2, 2023

Btw are you sure in testing, you uninstalled all termux apps, rebooted, and reinstalled, since otherwise untrusted_app_27 selinux process would still be assigned to the app, you can confirm with Termux in-app settings -> About, just making sure.

The executable being /system/bin/linker64 issues may be significant though.

Thanks, I noticed the behaviour you described as well, was really confusing first! But now I can confirm that I am indeed testing with effective targetSdk of 34! I'll share a branch of termux-app for testing shortly.

@fornwall
Copy link
Member Author

fornwall commented Oct 2, 2023

I think we should focus on the technical issue here, and leave Google Play policy out of the discussion, since I think that's a separate one. This change is hopefully good on its own, for bumping targetSdk also for distribution outside of Google Play.

@agnostic-apollo
Copy link
Member

Thanks, I noticed the behaviour you described as well, was really confusing first! But now I can confirm that I am indeed testing with effective targetSdk of 34! I'll share a branch of termux-app for testing shortly.

Welcome. Android also sometimes assigns latest target sdk SE_INFO, even if using older target sdk, and requires a reboot to fix. Have added checks for that too locally. I am far ahead of termux-app master locally, most of the non-terminal stuff has been rewritten/refactored, will be pushing in coming weeks when I manage to complete the changes.

leave Google Play policy out of the discussion

Fair enough. targetSdkVersion is far important, I also don't care too much about playstore either.

@agnostic-apollo

This comment was marked as off-topic.

@termux termux deleted a comment from ETERNALBLUEbullrun Oct 2, 2023
@ETERNALBLUEbullrun

This comment was marked as off-topic.

@twaik
Copy link
Member

twaik commented Oct 2, 2023

That solution may be problematic. Linker on old Android version does not allow to do this. But I am not sure when it starts.

@chenxiaolong
Copy link

That solution may be problematic. Linker on old Android version does not allow to do this. But I am not sure when it starts.

Looks like it was introduced in https://android.googlesource.com/platform/bionic/+/8f639a40966c630c64166d2657da3ee641303194, which was first included in Android 8.

@Grimler91
Copy link
Member

That solution may be problematic. Linker on old Android version does not allow to do this. But I am not sure when it starts.

Looks like it was introduced in https://android.googlesource.com/platform/bionic/+/8f639a40966c630c64166d2657da3ee641303194, which was first included in Android 8.

Only supporting android >= 8 (or even a version or two higher) for an app re-introduced to google play seems reasonable to me

@sylirre
Copy link
Member

sylirre commented Oct 2, 2023

Android 10+ solution is not needed to be used on older versions. It can be enabled on-demand, possibly with letting user to opt-out from this solution. Don't forget that:

  • Not everyone uses Android 10 or higher there. Some people actually use Termux to make obsolete devices useful, e.g. to run home server.
  • Some people have SELinux in permissive mode, whether intentionally or because custom ROM doesn't support enforcing. In this case exec restriction doesn't work.

Though I found one problem with this solution: users will no longer be able to run static binaries. When I attempt to run static binary, I get error unexpected e_type: 2. Can't remember any Termux package using static binaries, but sideloaded third-party prebuilt software definitely will no longer run without proot.

@agnostic-apollo
Copy link
Member

apt User-Agent patch would need to be updated to use TERMUX_EXEC__PROC_SELF_EXE.

https://github.com/termux/termux-packages/blob/master/packages/apt/0012-termux-id.patch

$ apt -oDebug::pkgAcquire::Worker=1 -oDebug::Acquire::http=1 -oDebug::Acquire::https=1 update 2>&1 | grep User-Agent
User-Agent: Debian APT-HTTP/1.3 (2.7.7) Termux (com.termux; install-prefix:/apex/com.android.runtime/bin/linker64)

@sylirre
Copy link
Member

sylirre commented Jan 20, 2024

apt User-Agent patch would need to be updated to use TERMUX_EXEC__PROC_SELF_EXE.

Better to drop patch then.

The whole idea of patch is to generate user agent identifier usable to distinguish official Termux from forks. In this case repo server or proxy (CloudFlare) can be configured drop unwanted parties and reduce the traffic. Identifier (installation prefix) has to be obtained programmatically in a way that can't be influenced by user through configuration or environment variable change.

This is not much actual for now as we have a lot of mirrors instead of single-server setup.

@agnostic-apollo
Copy link
Member

Yeah, makes sense what you say, a user can unset LD_PRELOAD and export custom value for $TERMUX_EXEC__PROC_SELF_EXE, but we can always compare owner of file by running stat against current process owner. If user tried to export a prefix for a different app than their own, then stat would fail and we do not use fallback in that case either.

But I am fine with dropping the patch too.

sylirre added a commit to termux/termux-packages that referenced this pull request Jan 20, 2024
Why remove the patch:

 termux/termux-exec#24

 These changes in termux-exec would cause apt to send invalid
 installation prefix in user agent.

 This patch is not actual for now and can be removed anyway.

***

Background why the patch was added earlier:

 Its only purpose to generate HTTP user agent string specific for
 environment. This lets us to distinguish on server side whether
 user download packages using official Termux app or derivative
 checking the installation prefix path and disallow downloads
 made by non-Termux projects to reduce egress traffic when needed.
WTNLXTBL added a commit to WTNLXTBL/termux-packages that referenced this pull request Jan 20, 2024
Why remove the patch:

 termux/termux-exec#24

 These changes in termux-exec would cause apt to send invalid
 installation prefix in user agent.

 This patch is not actual for now and can be removed anyway.

***

Background why the patch was added earlier:

 Its only purpose to generate HTTP user agent string specific for
 environment. This lets us to distinguish on server side whether
 user download packages using official Termux app or derivative
 checking the installation prefix path and disallow downloads
 made by non-Termux projects to reduce egress traffic when needed.
WTNLXTBL added a commit to WTNLXTBL/termux-packages-pacman that referenced this pull request Jan 20, 2024
Why remove the patch:

 termux/termux-exec#24

 These changes in termux-exec would cause apt to send invalid
 installation prefix in user agent.

 This patch is not actual for now and can be removed anyway.

***

Background why the patch was added earlier:

 Its only purpose to generate HTTP user agent string specific for
 environment. This lets us to distinguish on server side whether
 user download packages using official Termux app or derivative
 checking the installation prefix path and disallow downloads
 made by non-Termux projects to reduce egress traffic when needed.
termux-pacman-bot added a commit to termux-pacman/termux-packages that referenced this pull request Jan 20, 2024
Why remove the patch:

 termux/termux-exec#24

 These changes in termux-exec would cause apt to send invalid
 installation prefix in user agent.

 This patch is not actual for now and can be removed anyway.

***

Background why the patch was added earlier:

 Its only purpose to generate HTTP user agent string specific for
 environment. This lets us to distinguish on server side whether
 user download packages using official Termux app or derivative
 checking the installation prefix path and disallow downloads
 made by non-Termux projects to reduce egress traffic when needed.
xMeM pushed a commit to xMeM/termux-packages that referenced this pull request Jan 29, 2024
Why remove the patch:

 termux/termux-exec#24

 These changes in termux-exec would cause apt to send invalid
 installation prefix in user agent.

 This patch is not actual for now and can be removed anyway.

***

Background why the patch was added earlier:

 Its only purpose to generate HTTP user agent string specific for
 environment. This lets us to distinguish on server side whether
 user download packages using official Termux app or derivative
 checking the installation prefix path and disallow downloads
 made by non-Termux projects to reduce egress traffic when needed.
@0-Whoami
Copy link

0-Whoami commented Feb 10, 2024

Steps (on aarch64 - let me know if you want to test on some other arch):

I want to test this on x86-64 and arm

@twaik
Copy link
Member

twaik commented Feb 10, 2024

@0-Whoami

  1. No need to quote the whole post.
  2. It will be available when it is good and ready.

bigbio2002 pushed a commit to bigbio2002/termux-packages that referenced this pull request Feb 27, 2024
Intead of checking /proc/self/exe. This allows adb to be used with
termux-exec changes in termux/termux-exec#24.
bigbio2002 pushed a commit to bigbio2002/termux-packages that referenced this pull request Feb 27, 2024
Why remove the patch:

 termux/termux-exec#24

 These changes in termux-exec would cause apt to send invalid
 installation prefix in user agent.

 This patch is not actual for now and can be removed anyway.

***

Background why the patch was added earlier:

 Its only purpose to generate HTTP user agent string specific for
 environment. This lets us to distinguish on server side whether
 user download packages using official Termux app or derivative
 checking the installation prefix path and disallow downloads
 made by non-Termux projects to reduce egress traffic when needed.
@yujincheng08
Copy link

yujincheng08 commented Jun 8, 2024

  1. Instead of LD_PRELOAD, we can use seccomp to hook execve, which can preserve across fork, clone, and execve, and is useful for static elf as we cannot LD_PRELOAD them.
  2. For static elf, do linker static_helper, where the static_helper is a simple linker to mmap the static elf according to its elf header and then call its entry point.
  3. For /proc/self/exe, we can use PR_SET_MM_EXE_FILE; in detail, unmap all x memories of the linker while backup their addresses, offsets, and lengths, map some anon memories to these address to occupied their place, call PR_SET_MM_EXE_FILE, and then mmap the linker to their old addresses with MAP_FIXED.

@SomeDevHere
Copy link

SomeDevHere commented Jun 12, 2024

3. For `/proc/self/exe`, we can use `PR_SET_MM_EXE_FILE`; in detail, unmap all `x` memories of the linker while backup their addresses, offsets, and lengths, map some anon memories to these address to occupied their place, call `PR_SET_MM_EXE_FILE`, and then mmap the linker to their old addresses with `MAP_FIXED`.

PR_SET_MM_EXE_FILE requires the CAP_SYS_RESOURCE capacity so it likely cannot be used.
An incomplete workaround would be to have any syscall that opens a file descriptor from a path would need to be wrapped with seccomp/ptrace and override the result if it points to '/proc/self/exe'.

@twaik
Copy link
Member

twaik commented Jun 21, 2024

It seems like overwriting the content of __progname variable changes contents of /proc/$PID/cmdline so we can use it to make programs think they are executed without linker as first (zero?) argument.
I do not think we can do it via LD_PRELOAD since I did not find direct way to obtain pointer to original argc using only __progname pointer. Also it seems like __progname pointer and address of argv[0] are not the same.
Probably we should modify the crtbegin.c to invoke some code allocating some memory for new raw_args (of type KernelArgumentBlock), and fill it with data from original raw_args without linker path and with argc decreased by 1 and overwrite original raw_args. It will not change the symlink of program in /proc/$PID/exe but at least will let programs like top or ps show correct cmdline.
I am not sure how exactly we can distribute and use modified crtbegin code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.