SELinux is a security enhancement to Linux which allows users and administrators more control over access control.
Access can be constrained on such variables as which users and applications can access which resources. These resources may take the form of files. Standard Linux access controls, such as file modes (-rwxr-xr-x) are modifiable by the user and the applications which the user runs. Conversely, SELinux access controls are determined by a policy loaded on the system which may not be changed by careless users or misbehaving applications.
SELinux also adds finer granularity to access controls. Instead of only being able to specify who can read, write or execute a file, for example, SELinux lets you specify who can unlink, append only, move a file and so on. SELinux allows you to specify access to many resources other than files as well, such as network resources and interprocess communication (IPC).
So you think its easy writing a selinux policy? It actually is - I mean .. technically.
There are tools which auto generate policies from a log but are they good to use? It depends. and here it stops being easy as you, the integrator have to have a deep understanding what the IMPACT of each policy has.
To be able to go on you should really go through this first:
- SELinux FAQ
- https://www.billauer.co.il/selinux-policy-module-howto.html
- https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html-single/using_selinux/index
- and really make use of your favorite search engine!
as it helps a lot to understand how it works. and why is that important? because only if you know how it works you can make a decision if an auto generated policy is doing any harm or not.
On Ubuntu you can install audit2allow like this: apt install policycoreutils-python-utils
the process usally is:
- fetching the denials:
- using Linux:
adb logcat -b all |grep avc: > /tmp/denies
- using Windows:
adb shell "logcat -b all |grep avc: > /sdcard/Download/denies"
+adb pull /sdcard/Download/denies
- using Linux:
- creating the current policy in your android sources dir
source build/envsetup.sh
+lunch <device>
as usualmka libselinux sepolicy
- autogen policies:
cat /tmp/denies |audit2allow -p out/target/product/<device-codename>/root/sepolicy
- check the output + add the policies to their according filename (ensure you don't open security holes)
- start to build the sepolicies (
mka sepolicy
) - fix neverallows, etc occurring during the build
- if all is fixed build the full OS as usual
Step 6 in particular will have you tearing your hair out.. and that's why it is so important to understand the whole thing or learn by doing - keep in mind, however, that workarounds or too wide policies open holes that negate the security benefits of SELinux.
In order to debug you will require root permissions. Either you do an eng
build or userdebug
(enable adb root in developer options) or last but not least using a root tool like Magisk
.
- For debugging and testing live without building hundreds times you should download the se-tools or use magiskpolicy / aka
supolicy
- put the ones matching your device architecture to your device, e.g.
adb push setools-arm64-v8a/se* /sdcard/Download/
adb shell
(then "su" when using magisk)
mount -oremount,rw / (or "/vendor")
cp /sdcard/Download/se* /vendor/bin/
chmod +x /vendor/bin/se*
- now you can make use of:
seinfo
to see the current active policy stats (and more--help
)sesearch
(e.g.sesearch --allow -s cameraserver
) to see the current active rulessepolicy-inject
to inject policies on-the-fly!
Also a good-to-know hint: you can grab any existing policy from the device and generate a readable conf like this:
adb pull /sys/fs/selinux/policy sepolicy
checkpolicy -M -F -b -o policy.conf sepolicy
or if you build and did "mka sepolicy" before/after your build you can do that:
checkpolicy -M -F -b -o policy.conf out/target/product/<device>/root/sepolicy
in rare situations (like with the LG G4 camera) you might never see a selinux denial, neither in dmesg nor in logcat. this CAN happen yes. the reason for that is a dontaudit
rule which silently denies (obviously).
when it comes to debugging these the most easiest way is replacing any dontaudit rule by a deny
. while that sound easy first it might turn out harder than thought.
note: first approach is using strace in order to identify issues. keep in mind that denials also happens when the user simply has no file/dir permission! It is not always a selinux fault ;)
here is an ultimative approach using magiskpolicy (req. Magisk - obviously)
if something is not working as it should when you set selinux to enforcing you can test like that:
- run the process/service in the expected context:
adb shell
su
# stop any service which might run the binary!
su - <CORRECT-USERNAME-OF-THE-PROCESS>
strace -tt -y -a 120 -s 600 -ff runcon <<YOUR-CONTEXT>> <<YOUR-BINARY>> 2>&1 | grep -vE "ppoll|unfinished|resume"
- if you cannot identify the root cause, run it in the magisk context which is usually
u:r:magisk:s0
(but verify this first!):
adb shell
su
# stop any service which might run the binary!
su - <CORRECT-USERNAME-OF-THE-PROCESS>
strace -tt -y -a 120 -s 600 -ff runcon u:r:magisk:s0 <<YOUR-BINARY>> 2>&1 | grep -vE "ppoll|unfinished|resume"
- if it WORKS then you might can reveal
avc:
policy denials in your logs. but.. if not: - duplicate the magisk ruleset:
adb shell
su
# backup origin ruleset - IMPORTANT for testing w/o reboot!
magiskpolicy --print-rules > /sdcard/Download/selinux_origin.rules
cp /sys/fs/selinux/policy /sdcard/Download/sepolicy.origin
# duplicate ruleset and match it to the correct context:
magiskpolicy --print-rules | grep magisk | sed 's/ magisk / <<YOUR-CONTEXT>> /g' > /sdcard/Download/mycontext_all.rules
magiskpolicy --live --apply /sdcard/Download/mycontext_all.rules
- now you have all magisk policies set on your own context as well. this should definitively be enough to run it but obviously way too much. anyways. test to run your service/process, it should work as it should.
- next will be the most annoying part, you need to find out the required rules only
adb pull /sdcard/Download/mycontext_all.rules all_rules
wc -l all_rules # -> this will print e.g. 20000 (rules)
# next split it into 2 pieces. so if the wc command gave e.g 20000 use 10000 for XXXX here:
split -l XXXX all_rules all_rules_
# this will create 2 files all_rules_aa and all_rules_ab, now apply the first one and test if your issue is resolved:
POL=all_rules_aa; adb push $POL /sdcard/Download/ && adb shell "su -c magiskpolicy --live --apply /sdcard/Download/$POL"
# if it worked after that you know that all required rules are in all_rules_aa and you can skip all_rules_ab
# if it does NOT work then you MIGHT have all required rules in all_rules_ab but you need to test that.
# rollback to the original ruleset first!
adb shell "magiskpolicy --live --load /sdcard/Download/sepolicy.origin";
# now test the second one:
POL=all_rules_ab; adb push $POL /sdcard/Download/ && adb shell "su -c magiskpolicy --live --apply /sdcard/Download/$POL"
# if it worked: GREAT then you KNOW it must be in all_rules_ab ! but what if not?
# then you know it must be something required within BOTH. you had reverted to origin and applied the second half only
# so it neither the first nor the second works alone you need smth from both.
# so how to proceed when that happens?
# focus on 1 half first. I usually process the second one later. so I apply always the second one as it is first and then split
# the first one into pieces until finally found the missing rule(s)
# so in that case I would flash the ORIGIN first, then "all_rules_ab" which I want to handle LATER:
adb shell "magiskpolicy --live --load /sdcard/Download/sepolicy.origin"; POL=all_rules_ab; adb push $POL /sdcard/Download/ && adb shell "su -c magiskpolicy --live --apply /sdcard/Download/$POL"
# now I can proceed with all_rules_aa and narrow down this first:
wc -l all_rules_ab
split -l <HALF-AMOUNT-OF-WC-COMMAND> all_rules_aa all_rules_aa_
# this will produce "all_rules_aa_aa" and "all_rules_aa_ab".
# I prefer this specific file naming as it is easier to understand where a splitted part arised from.
# yes that can easily result in smth like "all_rules_ab_aa_aa_aa_ab_ac_ab_ab_ac" ;)
# from here everything repeats obviously.
# first rollback, apply the second half we want to do later:
adb shell "magiskpolicy --live --load /sdcard/Download/sepolicy.origin"; POL=all_rules_ab; adb push $POL /sdcard/Download/ && adb shell "su -c magiskpolicy --live --apply /sdcard/Download/$POL"
# then the new splitted part:
POL=all_rules_aa_aa; adb push $POL /sdcard/Download/ && adb shell "su -c magiskpolicy --live --apply /sdcard/Download/$POL"
# TEST!
# if it's NOT working proceed with all_rules_aa_ab, then all_rules_aa_aX ..
# if it IS working, proceed to split the working part again as before.
# keep in mind that it can always happen again that there are rules in both halfs but now you know how to
# handle these (see above)
# once you reached a small amount, like e.g. 10 lines per file or you want to test single policies here is how:
adb shell "su -c magiskpolicy --live 'allow .......'"
# TEST!
# if it does NOT work, proceed with the next without restoring origin
# once it WORKS, restore origin and apply the LAST applied rule first, test again, if it does not work you might need one of the previous one(s)
# always keep in mind that you might need not just 1 but multiple!
# when you reached that point you will finally found the 1 or more policies.
# store them in e.g. "REQUIRED.rules" and apply these always after restoring the origin ruleset.
# e.g.:
adb shell "magiskpolicy --live --load /sdcard/Download/sepolicy.origin"; POL=REQUIRED.rules; adb push $POL /sdcard/Download/ && adb shell "su -c magiskpolicy --live --apply /sdcard/Download/$POL"
# at one day you will identify ALL required policies. when that happened congrats.. Have a nice drink and toast to me ;)
# but serious, while the hardest work is over you are not finished yet.
# the magiskpolicy ruleset is VERY wide open. so you need to find the required permissions for each rule
# copy the REQUIRED.rules and just keep the permissions which are obviously needed for a rule (e.g. "write" when there is a write operation etc)
# test again, strace can give first hints like RD_ONLY, or O_RDWR on a file/dir, or "connect" etc
# lets say you reach THIS point and have all the rules optimized to their absolute minimal needs
# then the only thing left is reformat these into ".te" readable format (add a semicolon at the end, use a colon instead of a space for the class)
# yea and then.. CLEAN build.. pray, test and say hooray (if it worked)
while the above approach ensures to apply the original ruleset first there are (very rare) cases where something is acting weird. from time to time it is always a good idea to reboot the device and test the REQUIRED.rules and current state to ensure you didn't were caught by such a rare case.
!!! You do NOT need to solve ALL denials you find !!!
Denials in your log are not necessarily something you should fix, really. The rule of thumb is that you fix denials ONLY when they fix a problem - and if you are sure (enough) it is worth to write an allow (i.e. no breaking the security).