2

I have an executable build from a ndk-build program. I want to run it on a rooted Android device. How much is the possibility that Android will kill my executable?

Using adb shell, I am able to execute my executable using following commands:

adb push executable /sdcard
adb shell
device:/ $ su
device:/ # cd /system
device:/system # mount -o rw,remount /system
device:/system # mv /sdcard/executable .
device:/system # ./executable

My requirement is to run this executable on device boot.

I have tried following:

  1. Write ./executable in init.rc.
    • On reboot init.rc reset to its original contents. I figured out that Magisk done this.
  2. Write commands in /system/etc/init/bootstat.rc
    • ./executable
    • service custom /system/executable
    • on boot ./system/dhandler/diag_revealer

None of the above thing is working.

2

How much is the possibility that Android will kill my executable?

Privileged native processes usually don't get killed by Android except if they can't handle an error occurred inside, such as some system resource not available or permission denied because of SELinux etc. To free up memory, Android only kills processes within its framework i.e. running under zygote. To manage resources for native processes, Android uses cgroups.

Processes are killed when they receive SIGNALS from kernel or other userspace programs (e.g. with kill command) (1, 2). Kernel is the actual operating system, not visible to us but handling everything we do with device. A developer can program his code how to react to a specific signal if received, or completely ignore it (3). Except SIGKILL. Which can't be handled by program, no warning from kernel, no grace period to exit safely, just being terminated immediately. But kernel won't mind your presence unless he gets short on hardware resources or you start misbehaving. That's why programming is important.

Programs can send each other signals (including KILL), which are forwarded by kernel, governed by UID (4). However, init the very first process in userspace started by kernel is the dear one, kernel never forwards dangerous signals to init. And if this happens for some reason, kernel gets panic and reboots (5).

Summarizing above lines, it is possible to avoid being killed (AMAP) programmatically or using some scripting tricks as @alecxs has mentioned. But if you want to make sure that your process should restart if gets killed, define an Android init service.

On reboot init.rc reset to its original contents. I figured out that Magisk done this.

No, Magisk didn't do this. Android's rootfs is a temporary filesystem (not a persistent one like on /system or /data) that gets cleared on every reboot. Contents of root directory (/) are extracted from another partition named boot which contains kernel and ramdisk (though things have changed with system-as-root). So you can't change init.rc permanently unless you extract, modify, repack and reflash boot.img.

But to define a new init service, modifying init.rc isn't necessary. Android parses all .rc files from /etc/init directories located under /system and /vendor (6). So you can create your own .rc file.


NOTE: In order to get real root privileges and to deal with SELinux, all of the options given below depend on Magisk. See this answer for details.

INIT.D SCRIPT

You can use traditional init.d-like feature of Magisk to start a process on boot. Create script /data/adb/service.d/custom.sh:

#!/system/bin/sh

# write log file if executable throws something at stdout/sterr
exec >>/data/media/0/executable.log 2>&1

# run script in background to avoid blocking boot chain
[ -n "$BG" ] || { BG=Y "$0" & exit; }

# try to ignore signals as much as possible
for i in $(seq 64); do trap '' "$i"; done

# execute script whenever exits e.g. when executable gets killed
trap "sleep 5; exec $0" EXIT

# avoid multiple instances e.g. if script killed but executable is running
pkill -9 -x /system/bin/executable

# execute the binary, should run in foreground, otherwise get in loop
echo "$(date): Starting program..."
/system/bin/executable

# program is killed, won't reach here if script is killed
echo "$(date): Re-executing script..."

* EXIT is shell's pseudo-signal.
* Android's /system/bin/pkill (from toybox) is buggy, better use busybox applet.

Place the executable under /system/bin and set permissions:

~# chown 0.0 /system/bin/executable /data/adb/service.d/custom.sh
~# chmod 0755 /system/bin/executable /data/adb/service.d/custom.sh

You can also place script under /data/adb/post-fs-data.d/ but that's executed a bit earlier. Be sure that filesystem paths (and other required resources if any) are available at that stage.

EXECUTE PROGRAM FROM INIT

Another way is to directly execute the binary from init. Create custom.rc file:

#/etc/init/custom.rc

# execute the binary when boot is completed
on property:sys.boot_completed=1
    exec_background u:r:magisk:s0 -- /system/bin/executable

Set permissions:

~# chown 0.0 /etc/init/custom.rc
~# chmod 0644 /etc/init/custom.rc
~# chcon u:object_r:system_file:s0 /etc/init/custom.rc

And that's all! Restart device for changes to take effect.

However it's a one time execution, won't be restarted. Also there are some shell scripting features not available in .rc files. For instance you can't redirect stdout/stderr to a file, this has to be handled by executable program itself. So we can try to make use of both; shell script and .rc file:

INIT SERVICE

Instead of directly executing binary from .rc file, execute a shell script. Create script /system/bin/custom.sh:

#!/system/bin/sh

# write log file if executable throws something at stdout/sterr
exec >>/data/media/0/executable.log 2>&1

# execute the binary, should run in foreground, otherwise get in loop
echo "$(date): Starting program..."
exec /system/bin/executable

Create init service:

#/etc/init/custom.rc

# define service, use executable here if script not needed
service custom /system/bin/custom.sh

    # don't start unless explicitly asked to
    disabled

    # only execute once, don't restart if exited
    # don't add if you want to restart service when killed
    #oneshot

    # run with unrestricted SELinux context to avoid avc denials
    # it's required if SELinux is enforcing and service needs access
    # to some system resources not allowed by default sepolicy
    seclabel u:r:magisk:s0

# start the service when boot is completed
on property:sys.boot_completed=1
    start custom

Set permissions on executable, custom.sh and custom.rc as stated above and restart.

Other parameters (7) such as user, group, capabilities are required if you want to run the service as non-privileged user. Granting least required privileges is the recommended approach from security's perspective. See this answer for more details on capabilities and SELinux.

init will keep on restarting service every 5 seconds (by-default) if it gets killed. You can stop the service with setprop ctl.stop custom. Replace stop with start to start again.
To see what happens with service: dmesg | grep init: | tail.

  • I need "EXECUTE PROGRAM FROM INIT" because I don't want any shell script functionality and the required things are already handled by executable. Its working fine. Thank you for your help and time. – Vatish Sharma Jun 14 at 4:39
  • Please also help me here: superuser.com/questions/1448593/… – Vatish Sharma Jun 14 at 5:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.