Let's say I'm currently as root in Bash on a terminal, outside any app like Termux. I'm running Magisk.

Now I want to synchronously invoke Termux's Bash, such that it behaves the same way as it would if I had directly started the Termux app (i.e. with the same permissions and SELinux context and user/group and everything else).

How can I do this?


1 Answer 1


Android framework takes care of a lot of things when forking an app from zygote. In short, it's nearly impossible to run an app process from commandline so that it behaves “exactly the same way” as if the app was properly launched. Continue reading to have an idea of why it's so.

In order to run bash in Termux app's context so that it behaves exactly the same way as if it was started from the app, it must be run at least:

  • With UID/GID of the app assigned by the Package Manager at the time of installation
  • With supplementary group inet (3003), which is required to create network sockets and is assigned to any app at the time of installation which declares android.permission.INTERNET in its AndroidManifest.xml. For details see How Android's permissions mapping with UIDs/GIDs works?.
  • With supplementary group everybody (9997) which is used to control app's read / write access to /sdcard. For details see What is the “u#_everybody” UID?.
  • With supplementary groups <uid>_cache (UID+10000) and all_<uid> (UID+40000) so that app can access its cache, native executable code etc. But these aren't required necessarily since we are not running the app itself.
  • By entering the mount namespace of Termux app (com.termux process) which is required for supgid 9997 to work. If the app isn't running (e.g. if running from adb shell), supgid 1015 can be used to get write access to /sdcard but that's not how the app works.
  • By dropping all Linux capabilities (granular root privileges in Effective, Permitted, Inheritable, Bounding and Ambient sets) as well as setting securebits and process control attribute NO_NEW_PRIVS which make sure that app isn't able to elevate privileges by making use of setuid or file capabilities.
  • With blocked syscalls by applying seccomp-bpf filter.
  • With SELinux context of the app, which is determined at install / runtime based on MMAC (1, 2).
  • By adding the process to relevant cgroups e.g. cpuset, memcg etc. as the Android's framework does.
  • By catching, blocking and ignoring the same signals as the app would do.
  • By setting the environment it relies on. E.g. Termux's bash won't execute without LD_LIBRARY_PATH which it requires for dynamic linking.

There isn't a single commandline tool available which can do all of this sandboxing, it can be achieved fully only programmatically (see Minijail), or might be using multiple tools e.g. nsjail/firejail for setting seccomp filters, runcon to change SELinux context, capsh to alter DAC/capabilities, nsenter to enter mount namespace etc. setpriv from util-linux package can do the maximum:

~# uid=$(stat -c %u /data/data/com.termux)
~# pid=$(pidof -s com.termux)
~# label=$(cat /proc/$pid/attr/current)
~# export LD_LIBRARY_PATH=/data/data/com.termux/files/usr/lib
~# exec nsenter -t $pid -m setpriv --reuid $uid --regid $uid --groups 3003,9997 --bounding-set -all --selinux-label $label -- /system/bin/sh -c 'exec /data/data/com.termux/files/usr/bin/bash'

* nsenter and setpriv are busybox applets but with limited functionality. For aarch64 you may get static binaries here: nsenter, setpriv.

However we need to define shell_exec (label of /system/bin/sh) as entrypoint for execution by untrusted_app context which is not part of stock sepolicy (at least on Pie):

~# supolicy --live 'allow untrusted_app shell_exec file entrypoint'

Executing /system/bin/sh before executing /data/data/com.termux/files/usr/bin/bash is required because /data partition is mounted with nosuid option which prevents SELinux context transition (from magisk to untrusted_app) (3) and you get permission denied. You may consider mounting /data without nosuid to skip this step.
For the same reason --no-new-privs and --selinux-label can't be used in conjunction.

All this happens in native world, nothing in Java stack. So we have no direct control on things like manifest permissions which entirely operate inside Android framework. However manifest permission enforcement is also based on UIDs (4). For instance if Termux was granted android.permission.WRITE_EXTERNAL_STORAGE the bash we are running with Termux's UID will also be able to write to /sdcard.

From your comment:

It doesn't make sense to me how this is possible. Like, consider the output:

u0_a129 ~$ /sbin/su --context=u:object_r:app_data_file:s0:c512,c768 u0_a129 -c /system/bin/id
uid=10129(u0_a129) gid=10129(u0_a129) groups=10129(u0_a129) context=u:r:magisk:s0
u0_a129 ~$ /system/bin/id
uid=10129(u0_a129) gid=10129(u0_a129) groups=10129(u0_a129),3003(inet),9997(everybody),20129(u0_a129_cache),50129(all_a129),99909997(u999_everybody) context=u:r:untrusted_app_27:s0:c512,c768

Well, according to my understanding that's how Unix Discretionary Access Control works. First of all passing --context to Magisk's /sbin/su makes no difference as explained here:

the option still exists for CLI compatibility with apps designed for SuperSU. However the option is silently ignored since it's no longer relevant.

So the context isn't switched as you can see it's still u:r:magisk:s0. Secondly Magisk's su isn't the actual switch user (5), it provides a minimal functionality of standard su binary (the one we have on Linux). For details see How Magisk works?

In your first command it set UID, GID and supplementary groups to the UID you provided. You didn't ask su to set any additional supplementary groups, neither it can.

In second command you see the UID, GID, supplementary groups and SELinxu context which were set by zygote when it forked the app's DVM/ART (com.termux in case of Termux). Supplementary groups are explained above. 99909997 is also explained in the link given above.

  • It would be nice (hint, hint!) if the termux developers would build some sort of cli-callable utility to perform all of these functions, so that we can write commands that utilize termux capabilities for Magisk, adb, and other utilities. And note that termux-boot does not apparently run with all of these conventions, and programs initiated via termux-boot fail -- even on my rooted device -- because one or more of these necessary capaibilites are not set when the boot scripts run. IMHO, termux would be much more useful if such a termux helper program was available.
    – HippoMan
    Commented May 22, 2020 at 21:38
  • @HippoMan taking care of all of these prerequisites and sandboxing mechanisms (plus those I don't know of or missed) is not the responsibility of app developers, neither they can. It's the Android framework which fulfills all of these requirements when forking an app from zygote. Incorporating these functionalities in an app or a separate utility would be equivalent to including a reasonably large part of AOSP code in them. This answer is just a reference to have an idea of what things are taken care of when launching an app. In actual there are even more complications involved. Commented May 22, 2020 at 22:42
  • For Linux distros, there are --reuid, --regid, --groups, etc. available for setpriv; but I get setpriv: unrecognized option '--groups' in Android. Duh. Maybe there is something missing, or I'm using a wrong BusyBox. Maybe I must compile the tool myself. At last, setpriv does not work with the options mentioned. Commented Sep 9, 2020 at 17:26
  • @MAChitgarha I mentioned that bb applet is limited. And the static binary link is also given. Just checked it again for you: ibb.co/98nQ3g1 Commented Sep 9, 2020 at 23:24
  • @IrfanLatif Thanks for your attention. My phone architecture is arm, not aarch64. Do the files work on my platform, or I should make them myself? If the later, please post the source code of the library I should compile. Thanks. Commented Sep 10, 2020 at 11:06

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .