6

I am working on an application that needs root access and I have a device which is rooted but not with Magisk. This device has only adb shell root available. So, I need an alternative to call the required functionality without the use of Magisk or other tools. I did it by placing my executable inside system directory and run it as a daemon. This daemon required access on some location which is restricted by SELinux policies.

I have injected the required policy with following commands:

    sepolicy-inject -s init -t su -c process -p transition -l
    sepolicy-inject -s su -t system_file -c file -p entrypoint -l
    sepolicy-inject -s init -t su -c process -p rlimitinh -l
    sepolicy-inject -s init -t su -c process -p siginh -l
    sepolicy-inject -s su -t shell_exec -c file -p read -l
    sepolicy-inject -s su -t shell_exec -c file -p execute -l
    sepolicy-inject -s su -t shell_exec -c file -p getattr  -l
    sepolicy-inject -s su -t vendor_toolbox_exec -c file -p execute_no_trans -l
    sepolicy-inject -s init -t su -c process -p noatsecure -l
    sepolicy-inject -s su -t toolbox_exec -c file -p getattr -l
    sepolicy-inject -s su -t toolbox_exec -c file -p execute -l
    sepolicy-inject -s su -t system_file -c file -p execute_no_trans -l
    sepolicy-inject -s su -t storage_file -c dir -p search -l
    sepolicy-inject -s su -t storage_file -c lnk_file -p read -l
    sepolicy-inject -s su -t tmpfs -c dir -p search -l
    sepolicy-inject -s su -t mnt_user_file -c dir -p search -l
    sepolicy-inject -s su -t mnt_user_file -c lnk_file -p read -l
    sepolicy-inject -s su -t sdcardfs -c dir -p search -l
    sepolicy-inject -s su -t sdcardfs -c file -p append -l
    sepolicy-inject -s su -t toolbox_exec -c file -p read -l
    sepolicy-inject -s su -t toolbox_exec -c file -p open -l
    sepolicy-inject -s su -t sdcardfs -c file -p read -l
    sepolicy-inject -s su -t sdcardfs -c file -p write -l
    sepolicy-inject -s su -t sdcardfs -c file -p open -l
    sepolicy-inject -s su -t media_rw_data_file -c file -p read -l
    sepolicy-inject -s su -t media_rw_data_file -c file -p write -l
    sepolicy-inject -s su -t media_rw_data_file -c file -p open -l
    sepolicy-inject -s su -t media_rw_data_file -c file -p append -l

The problem is that they are not persistent after reboot. I know I can extract boot.img and ramdisk, replace /sepolicy with new policy file copied from /sys/fs/selinux/policy, repack boot.img and flash back.

I want to do it without reflashing boot.img. Is there any way with which I can execute above commands after Android finishes with generating SELinux files?

I have tried following rc files:

#/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

    # Use `seclabel u:r:magisk:s0` to run with unrestricted SELinux context to avoid avc denials
    # can also use "u:r:su:s0" on userdebug / eng builds if no Magisk
    # it's required if SELinux is enforcing and service needs access
    # to some system resources not allowed by default sepolicy
    seclabel u:r:su:s0

# start the service when boot is completed
on property:sys.boot_completed=1
    sepolicy-inject -s init -t su -c process -p transition -l
    sepolicy-inject -s su -t system_file -c file -p entrypoint -l
    sepolicy-inject -s init -t su -c process -p rlimitinh -l
    sepolicy-inject -s init -t su -c process -p siginh -l
    sepolicy-inject -s su -t shell_exec -c file -p read -l
    sepolicy-inject -s su -t shell_exec -c file -p execute -l
    sepolicy-inject -s su -t shell_exec -c file -p getattr  -l
    sepolicy-inject -s su -t vendor_toolbox_exec -c file -p execute_no_trans -l
    sepolicy-inject -s init -t su -c process -p noatsecure -l
    sepolicy-inject -s su -t toolbox_exec -c file -p getattr -l
    sepolicy-inject -s su -t toolbox_exec -c file -p execute -l
    sepolicy-inject -s su -t system_file -c file -p execute_no_trans -l
    sepolicy-inject -s su -t storage_file -c dir -p search -l
    sepolicy-inject -s su -t storage_file -c lnk_file -p read -l
    sepolicy-inject -s su -t tmpfs -c dir -p search -l
    sepolicy-inject -s su -t mnt_user_file -c dir -p search -l
    sepolicy-inject -s su -t mnt_user_file -c lnk_file -p read -l
    sepolicy-inject -s su -t sdcardfs -c dir -p search -l
    sepolicy-inject -s su -t sdcardfs -c file -p append -l
    sepolicy-inject -s su -t toolbox_exec -c file -p read -l
    sepolicy-inject -s su -t toolbox_exec -c file -p open -l
    sepolicy-inject -s su -t sdcardfs -c file -p read -l
    sepolicy-inject -s su -t sdcardfs -c file -p write -l
    sepolicy-inject -s su -t sdcardfs -c file -p open -l
    sepolicy-inject -s su -t media_rw_data_file -c file -p read -l
    sepolicy-inject -s su -t media_rw_data_file -c file -p write -l
    sepolicy-inject -s su -t media_rw_data_file -c file -p open -l
    sepolicy-inject -s su -t media_rw_data_file -c file -p append -l
    start custom

but it is not working because I think Android generates SELinux files after my custom service has been triggered.

Also tried above commands on onrestart option of init service but failed.

Any suggestion?

2
  • if timing is your only problem i would suggest sleep command
    – alecxs
    Jul 22, 2019 at 11:34
  • I have tried to add 5 minutes sleep with sleep 300 after on property:sys.boot_completed=1 but the service is started instantly on boot complete so this command is not working as expected. Aug 1, 2019 at 6:41

2 Answers 2

4

The init service you have defined won't inject SELinux policy rules because of two reasons:

  • The syntax of sepolicy-inject commands is incomplete; .rc files aren't shell scripts. The correct syntax would be:

    #/etc/init/custom.rc
    
    ...
    
    on property:sys.boot_completed=1
        exec - -- /system/bin/sepolicy-inject -s init -t su -c process -p transition -l
    
    ...
    
  • This will execute the statement with context u:r:init.s0. But modifying SELinux policy requires permission load_policy i.e. you need to inject rule sepolicy-inject -s init -t kernel -c security -p load_policy -l which again won't be allowed to init. Read What sepolicy context will allow any other context to access it? to know how SELinux is enforced on Android.

So you are in the same chicken or the egg situation as you were at the start of your previous question. SELinux policy can only be modified with context u:r:su:s0 which is available only through adb shell on userdebug builds of ROMs. Or root the device e.g. with Magisk, or replace /sepolicy file in boot.img.

NOTE: You don't need to define rules like sepolicy-inject -s su ... as u:r:su:s0 is already set permissive in policy.

sepolicy File Locations:

On pre-Treble Android releases, there are two possible locations defined for sepolicy file:

/sepolicy
/data/security/current/sepolicy

As stated here:

The Android initialisation / reload process will first check for this file at: /data/security/current/sepolicy. If not present then check root directory: /sepolicy.

However sepolicy is loaded by init at very early boot process when /data is not mounted. As stated here:

Since only the root filesystem is mounted, it chooses /sepolicy at this time. The other path is for dynamic runtime reloads of policy.

So initially /sepolicy is loaded for sure. But you can put modified sepolicy at other locations to see if the previous policy is (or could possibly be) overwritten at some later boot stage. You might need to copy other files as well; this post could be helpful. I never tried this.

On Oreo+ a monolithic policy is loaded from /sepolicy if the device is not a Treble device (or if Magisk patched init to force load /sepolicy). On Treble devices /system, /vendor and /odm are mounted by kernel before starting init, as configured in DTB. Here a pre-compiled split policy is loaded from /vendor/etc/selinux/precompiled_sepolicy if it matches with /system, or the policy is built from .cil files in /system/etc/selinux and /vendor/etc/selinux before loading. See details here.

In both situations things are far more complicated to try than simply replacing /sepolicy file in boot.img, which is not a big deal in my opinion. You can dd out a backup of your original boot.img to /sdcard which can be restored in a few seconds anytime. However bootloader needs to be unlocked to boot a modified boot.img.
Please note that on devices with system-as-root (A/B or others) ramdisk is moved to system.img. So all files which were previously part of boot.img (except kernel and DTB) are now part of system partition.

2
2

I know the question was asked quite some time ago, but since I tried doing something similar recently and this post was one of the top hits on Google, I want to share my recent experience to help others.

Note that this answer will focus solely on Android 9 to 13 and Treble devices.

If we want to know how to modify the SELinux policies, I believe it is good to first understand how the policies are loaded in the first place.

SELinux in the boot process

At an early stage of the Android boot process and after mounting most of the partitions, init will attempt to load the SELinux policies from a monolithic sepolicy file found at either /odm/etc/selinux/precompiled_sepolicy or /vendor/etc/selinux/precompiled_sepolicy. If init notices that the device is updated out of sync, init will instead recompile the SELinux policies based on .cil files that can be found on several locations.

You can find all the details in the comments of the init/selinux.cpp source file and by looking at the implementation, but here is a summary of the exact steps taken by init:

  1. Find the sepolicy file to use:
    • if /odm/etc/selinux/precompiled_sepolicy exists, use it as the sepolicy file to use
    • else, if /vendor/etc/selinux/precompiled_sepolicy exists, use it as the sepolicy file to use
    • else if neither of the above exists, stop here, recompile the policies based on the .cil files and load the newly compiled sepolicy file.
  2. Check if device was updated out of sync:
    • Compare /(odm|vendor)/etc/selinux/precompiled_sepolicy.plat_sepolicy_and_mapping.sha256 and /system/etc/selinux/plat_sepolicy_and_mapping.sha256
    • Compare /(odm|vendor)/etc/selinux/precompiled_sepolicy.system_ext_sepolicy_and_mapping.sha256 and /system_ext/etc/selinux/system_ext_sepolicy_and_mapping.sha256
    • Compare /(odm|vendor)/etc/selinux/precompiled_sepolicy.product_sepolicy_and_mapping.sha256 and /product/etc/selinux/product_sepolicy_and_mapping.sha256
    • (On Android 13) Compare /(odm|vendor)/etc/selinux/precompiled_sepolicy.apex_sepolicy.sha256 and /dev/selinux/apex_sepolicy.sha256
    • if any of the above pair of files do not match, stop here, recompile the policies based on the .cil files and load the newly compiled sepolicy file.
  3. If everything is ok, load the precompiled sepolicy file (/odm/etc/selinux/precompiled_sepolicy or /vendor/etc/selinux/precompiled_sepolicy)

The compilation of the policies based on the .cil files is as follows:

  1. Get the latest version of the policies that are compatible with the vendor version (/vendor/etc/selinux/plat_sepolicy_vers.txt, note that init does not look at the file in the /odm partition)
  2. Gather the .cil files to compile:
    • /system/etc/selinux/mapping/{vendor_version}.cil
    • /system/etc/selinux/mapping/{vendor_version}.compat.cil
    • /system_ext/etc/selinux/system_ext_sepolicy.cil
    • /system_ext/etc/selinux/mapping/{vendor_version}.cil
    • /product/etc/selinux/product_sepolicy.cil
    • /product/etc/selinux/mapping/{vendor_version}.cil
    • /vendor/etc/selinux/plat_pub_versioned.cil
    • /vendor/etc/selinux/vendor_sepolicy.cil or if it does not exist: /vendor/etc/selinux/nonplat_sepolicy.cil
    • /odm/etc/selinux/odm_sepolicy.cil
  3. Compile the above files with secilc to /dev/sepolicy.XXXXXX
  4. Load policies from /dev/sepolicy.XXXXXX

Modifying SELinux policies permantently

Now that we understand how init loads the policies, it is easy to see how one could permanently add new policies to the system:

  • Directly add the policies to /odm/etc/selinux/precompiled_sepolicy or /vendor/etc/selinux/precompiled_sepolicy with sepolicy-inject:
    sepolicy-inject -s su -t system_file -c file -p entrypoint -P /vendor/etc/selinux/precompiled_sepolicy -o /vendor/etc/selinux/precompiled_sepolicy
    
  • Make init thinks the device is updated out of sync (By modifying one of the above .sha256 files, or deleting the precompiled sepolicy files) and modify one of the above .cil files

Note that in both cases, you will need to have a read-write access to the partition you want to modify (/vendor, /odm, and/or /system). These partitions are normally mounted as read-only, so you will have to either:

  • Remount them as read-write if you have root access (either because your device is rooted, or using adb root on a userdebug Android build)
  • Boot in recovery mode, mount the partitions as read-write and modify them in recovery

If you do not want to have to remount the partitions every time you want to change the SELinux policies, you can add some SELinux rules to allow init to load new policies, thereby resolving the chicken and egg problem (as mentioned in this excellent answer) you had with your init script. You can find which SELinux rules are needed by searching for SELinux violations (avc errors) in the device logs on startup:

adb logcat | grep avc

With this, you will be able to have an init script that loads new policies with sepolicy-inject.

You must log in to answer this question.

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